Skip to content

Commit

Permalink
fix(callback): add error handling for callback service (#73)
Browse files Browse the repository at this point in the history
Refs: #67
Reviewed-By: Evelyn Gurschler <[email protected]>
  • Loading branch information
Phil91 authored Aug 2, 2024
1 parent 767e121 commit 9db2959
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 15 deletions.
13 changes: 10 additions & 3 deletions src/processes/DimProcess.Library/Callback/CallbackService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -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<CallbackService>(_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);
}
}
198 changes: 198 additions & 0 deletions tests/processes/DimProcess.Library.Tests/CallbackServiceTests.cs
Original file line number Diff line number Diff line change
@@ -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<CallbackSettings> _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<ITokenService>();
}

#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<CallbackService>(_options.Value, A<CancellationToken>._))
.Returns(httpClient);
var sut = new CallbackService(_tokenService, _options);

// Act
await sut.SendCallback("BPNL00001TEST", _fixture.Create<ServiceCredentialBindingDetailResponse>(), _fixture.Create<JsonDocument>(), "did:web:test123", CancellationToken.None);

// Assert
httpMessageHandlerMock.RequestMessage.Should().Match<HttpRequestMessage>(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<CallbackService>(_options.Value, A<CancellationToken>._)).Returns(httpClient);
var sut = new CallbackService(_tokenService, _options);

// Act
async Task Act() => await sut.SendCallback("BPNL00001TEST", _fixture.Create<ServiceCredentialBindingDetailResponse>(), _fixture.Create<JsonDocument>(), "did:web:test123", CancellationToken.None);

// Assert
var ex = await Assert.ThrowsAsync<ServiceException>(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<CallbackService>(_options.Value, A<CancellationToken>._))
.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<HttpRequestMessage>(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<CallbackService>(_options.Value, A<CancellationToken>._)).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<ServiceException>(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<CallbackService>(_options.Value, A<CancellationToken>._))
.Returns(httpClient);
var sut = new CallbackService(_tokenService, _options);

// Act
await sut.SendTechnicalUserDeletionCallback(externalId, CancellationToken.None);

// Assert
httpMessageHandlerMock.RequestMessage.Should().Match<HttpRequestMessage>(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<CallbackService>(_options.Value, A<CancellationToken>._)).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<ServiceException>(Act);
ex.Message.Should().Contain("call to external system send-technical-user-deletion-callback failed with statuscode");
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\database\Dim.DbAccess\Dim.DbAccess.csproj" />
<ProjectReference Include="..\..\..\src\processes\DimProcess.Library\DimProcess.Library.csproj" />
<ProjectReference Include="..\..\shared\Tests.Shared\Tests.Shared.csproj" />
</ItemGroup>
</Project>
36 changes: 36 additions & 0 deletions tests/shared/Tests.Shared/AutoFixtureExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<ThrowingRecursionBehavior>().ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
fixture.Customize<JsonDocument>(x => x.FromFactory(() => JsonDocument.Parse("{}")));
return fixture;
}
}
54 changes: 54 additions & 0 deletions tests/shared/Tests.Shared/HttpMessageHandlerMock.cs
Original file line number Diff line number Diff line change
@@ -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<HttpResponseMessage> 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;
}
11 changes: 2 additions & 9 deletions tests/shared/Tests.Shared/MockLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,14 @@ public interface IMockLogger<T>
void Log(LogLevel logLevel, Exception? exception, string logMessage);
}

public class MockLogger<T> : ILogger<T>
public class MockLogger<T>(IMockLogger<T> logger) : ILogger<T>
{
private readonly IMockLogger<T> _logger;

public MockLogger(IMockLogger<T> logger)
{
_logger = logger;
}

public IDisposable? BeginScope<TState>(TState state) where TState : notnull => new TestDisposable();

public bool IsEnabled(LogLevel logLevel) => true;

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) =>
_logger.Log(logLevel, exception, formatter(state, exception));
logger.Log(logLevel, exception, formatter(state, exception));

public class TestDisposable : IDisposable
{
Expand Down
7 changes: 4 additions & 3 deletions tests/shared/Tests.Shared/Tests.Shared.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Testcontainers" Version="3.9.0" />
<PackageReference Include="Testcontainers.PostgreSql" Version="3.9.0" />
<PackageReference Include="AutoFixture" Version="4.18.1" />
<PackageReference Include="FakeItEasy" Version="8.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
</ItemGroup>

</Project>

0 comments on commit 9db2959

Please sign in to comment.