diff --git a/src/processes/DimProcess.Library/Callback/CallbackService.cs b/src/processes/DimProcess.Library/Callback/CallbackService.cs index 5fc8565..cd0fa66 100644 --- a/src/processes/DimProcess.Library/Callback/CallbackService.cs +++ b/src/processes/DimProcess.Library/Callback/CallbackService.cs @@ -22,6 +22,7 @@ using Dim.Clients.Extensions; using DimProcess.Library.Callback.DependencyInjection; using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; using System.Net.Http.Json; using System.Text.Json; @@ -45,7 +46,9 @@ public async Task SendCallback(string bpn, ServiceCredentialBindingDetailRespons dimDetails.Credentials.Uaa.ClientId, dimDetails.Credentials.Uaa.ClientSecret) ); - await httpClient.PostAsJsonAsync($"/api/administration/registration/dim/{bpn}", data, JsonSerializerExtensions.Options, cancellationToken).ConfigureAwait(false); + await httpClient.PostAsJsonAsync($"/api/administration/registration/dim/{bpn}", data, JsonSerializerExtensions.Options, cancellationToken) + .CatchingIntoServiceExceptionFor("send-callback", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE) + .ConfigureAwait(false); } public async Task SendTechnicalUserCallback(Guid externalId, string tokenAddress, string clientId, string clientSecret, CancellationToken cancellationToken) @@ -56,13 +59,17 @@ public async Task SendTechnicalUserCallback(Guid externalId, string tokenAddress tokenAddress, clientId, clientSecret); - await httpClient.PostAsJsonAsync($"/api/administration/serviceAccount/callback/{externalId}", data, JsonSerializerExtensions.Options, cancellationToken).ConfigureAwait(false); + await httpClient.PostAsJsonAsync($"/api/administration/serviceAccount/callback/{externalId}", data, JsonSerializerExtensions.Options, cancellationToken) + .CatchingIntoServiceExceptionFor("send-technical-user-callback", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE) + .ConfigureAwait(false); } public async Task SendTechnicalUserDeletionCallback(Guid externalId, CancellationToken cancellationToken) { var httpClient = await tokenService.GetAuthorizedClient(_settings, cancellationToken) .ConfigureAwait(false); - await httpClient.PostAsync($"/api/administration/serviceAccount/callback/{externalId}/delete", null, cancellationToken).ConfigureAwait(false); + await httpClient.PostAsync($"/api/administration/serviceAccount/callback/{externalId}/delete", null, cancellationToken) + .CatchingIntoServiceExceptionFor("send-technical-user-deletion-callback", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE) + .ConfigureAwait(false); } } diff --git a/tests/processes/DimProcess.Library.Tests/CallbackServiceTests.cs b/tests/processes/DimProcess.Library.Tests/CallbackServiceTests.cs new file mode 100644 index 0000000..0ec0098 --- /dev/null +++ b/tests/processes/DimProcess.Library.Tests/CallbackServiceTests.cs @@ -0,0 +1,198 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Dim.Clients.Api.Cf; +using Dim.Tests.Shared; +using DimProcess.Library.Callback; +using DimProcess.Library.Callback.DependencyInjection; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; +using System.Net; +using System.Net.Http.Json; +using System.Text.Json; + +namespace DimProcess.Library.Tests; + +public class CallbackServiceTests +{ + #region Initialization + + private readonly ITokenService _tokenService; + private readonly IFixture _fixture; + private readonly IOptions _options; + + public CallbackServiceTests() + { + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.ConfigureFixture(); + + _options = Options.Create(new CallbackSettings + { + Password = "passWord", + Scope = "test", + Username = "user@name", + BaseAddress = "https://base.address.com", + ClientId = "CatenaX", + ClientSecret = "pass@Secret", + GrantType = "cred", + TokenAddress = "https://key.cloak.com", + }); + _fixture.Inject(_options); + _tokenService = A.Fake(); + } + + #endregion + + #region SendCallback + + [Fact] + public async Task SendCallback_WithValidData_DoesNotThrowException() + { + // Arrange + var httpMessageHandlerMock = + new HttpMessageHandlerMock(HttpStatusCode.OK); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)) + .Returns(httpClient); + var sut = new CallbackService(_tokenService, _options); + + // Act + await sut.SendCallback("BPNL00001TEST", _fixture.Create(), _fixture.Create(), "did:web:test123", CancellationToken.None); + + // Assert + httpMessageHandlerMock.RequestMessage.Should().Match(x => + x.Content is JsonContent && + (x.Content as JsonContent)!.ObjectType == typeof(CallbackDataModel) && + ((x.Content as JsonContent)!.Value as CallbackDataModel)!.Did == "did:web:test123" + ); + } + + [Fact] + public async Task SendCallback_WithInvalidData_ThrowsServiceException() + { + // Arrange + var httpMessageHandlerMock = new HttpMessageHandlerMock(HttpStatusCode.BadRequest); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)).Returns(httpClient); + var sut = new CallbackService(_tokenService, _options); + + // Act + async Task Act() => await sut.SendCallback("BPNL00001TEST", _fixture.Create(), _fixture.Create(), "did:web:test123", CancellationToken.None); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Contain("call to external system send-callback failed with statuscode"); + } + + #endregion + + #region SendTechnicalUserCallback + + [Fact] + public async Task SendTechnicalUserCallback_WithValidData_DoesNotThrowException() + { + // Arrange + var httpMessageHandlerMock = + new HttpMessageHandlerMock(HttpStatusCode.OK); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)) + .Returns(httpClient); + var sut = new CallbackService(_tokenService, _options); + + // Act + await sut.SendTechnicalUserCallback(Guid.NewGuid(), "https://example.org/token", "cl1", "test123", CancellationToken.None); + + // Assert + httpMessageHandlerMock.RequestMessage.Should().Match(x => + x.Content is JsonContent && + (x.Content as JsonContent)!.ObjectType == typeof(AuthenticationDetail) && + ((x.Content as JsonContent)!.Value as AuthenticationDetail)!.ClientId == "cl1" && + ((x.Content as JsonContent)!.Value as AuthenticationDetail)!.ClientSecret == "test123" + ); + } + + [Fact] + public async Task SendTechnicalUserCallback_WithInvalidData_ThrowsServiceException() + { + // Arrange + var httpMessageHandlerMock = new HttpMessageHandlerMock(HttpStatusCode.BadRequest); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)).Returns(httpClient); + var sut = new CallbackService(_tokenService, _options); + + // Act + async Task Act() => await sut.SendTechnicalUserCallback(Guid.NewGuid(), "https://example.org/token", "cl1", "test123", CancellationToken.None); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Contain("call to external system send-technical-user-callback failed with statuscode"); + } + + #endregion + + #region SendTechnicalUserDeletionCallback + + [Fact] + public async Task SendTechnicalUserDeletionCallback_WithValidData_DoesNotThrowException() + { + // Arrange + var externalId = Guid.NewGuid(); + var httpMessageHandlerMock = + new HttpMessageHandlerMock(HttpStatusCode.OK); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)) + .Returns(httpClient); + var sut = new CallbackService(_tokenService, _options); + + // Act + await sut.SendTechnicalUserDeletionCallback(externalId, CancellationToken.None); + + // Assert + httpMessageHandlerMock.RequestMessage.Should().Match(x => + x.RequestUri!.AbsoluteUri.Contains($"{externalId}/delete") + ); + } + + [Fact] + public async Task SendTechnicalUserDeletionCallback_WithInvalidData_ThrowsServiceException() + { + // Arrange + var httpMessageHandlerMock = new HttpMessageHandlerMock(HttpStatusCode.BadRequest); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)).Returns(httpClient); + var sut = new CallbackService(_tokenService, _options); + + // Act + async Task Act() => await sut.SendTechnicalUserDeletionCallback(Guid.NewGuid(), CancellationToken.None); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Contain("call to external system send-technical-user-deletion-callback failed with statuscode"); + } + + #endregion +} diff --git a/tests/processes/DimProcess.Library.Tests/DimProcess.Library.Tests.csproj b/tests/processes/DimProcess.Library.Tests/DimProcess.Library.Tests.csproj index 382251e..ad6c020 100644 --- a/tests/processes/DimProcess.Library.Tests/DimProcess.Library.Tests.csproj +++ b/tests/processes/DimProcess.Library.Tests/DimProcess.Library.Tests.csproj @@ -25,5 +25,6 @@ + diff --git a/tests/shared/Tests.Shared/AutoFixtureExtensions.cs b/tests/shared/Tests.Shared/AutoFixtureExtensions.cs new file mode 100644 index 0000000..2ee5b0b --- /dev/null +++ b/tests/shared/Tests.Shared/AutoFixtureExtensions.cs @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using AutoFixture; +using System.Text.Json; + +namespace Dim.Tests.Shared; + +public static class AutoFixtureExtensions +{ + public static IFixture ConfigureFixture(this IFixture fixture) + { + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + fixture.Customize(x => x.FromFactory(() => JsonDocument.Parse("{}"))); + return fixture; + } +} diff --git a/tests/shared/Tests.Shared/HttpMessageHandlerMock.cs b/tests/shared/Tests.Shared/HttpMessageHandlerMock.cs new file mode 100644 index 0000000..65aeb0d --- /dev/null +++ b/tests/shared/Tests.Shared/HttpMessageHandlerMock.cs @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using System.Net; + +namespace Dim.Tests.Shared; + +public class HttpMessageHandlerMock( + HttpStatusCode statusCode, + HttpContent? httpContent = null, + Exception? ex = null, + bool isRequestUri = false) + : HttpMessageHandler +{ + protected override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + RequestMessage = request; + + if (ex != null) + { + throw ex; + } + + var httpResponseMessage = new HttpResponseMessage(statusCode); + httpResponseMessage.RequestMessage = isRequestUri ? request : null; + if (httpContent != null) + { + httpResponseMessage.Content = httpContent; + } + + return Task.FromResult(httpResponseMessage); + } + + public HttpRequestMessage? RequestMessage { get; private set; } = null; +} diff --git a/tests/shared/Tests.Shared/MockLogger.cs b/tests/shared/Tests.Shared/MockLogger.cs index 951b1a8..ef4689c 100644 --- a/tests/shared/Tests.Shared/MockLogger.cs +++ b/tests/shared/Tests.Shared/MockLogger.cs @@ -27,21 +27,14 @@ public interface IMockLogger void Log(LogLevel logLevel, Exception? exception, string logMessage); } -public class MockLogger : ILogger +public class MockLogger(IMockLogger logger) : ILogger { - private readonly IMockLogger _logger; - - public MockLogger(IMockLogger logger) - { - _logger = logger; - } - public IDisposable? BeginScope(TState state) where TState : notnull => new TestDisposable(); public bool IsEnabled(LogLevel logLevel) => true; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) => - _logger.Log(logLevel, exception, formatter(state, exception)); + logger.Log(logLevel, exception, formatter(state, exception)); public class TestDisposable : IDisposable { diff --git a/tests/shared/Tests.Shared/Tests.Shared.csproj b/tests/shared/Tests.Shared/Tests.Shared.csproj index e53b728..67b675a 100644 --- a/tests/shared/Tests.Shared/Tests.Shared.csproj +++ b/tests/shared/Tests.Shared/Tests.Shared.csproj @@ -29,8 +29,9 @@ - - - + + + +