Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(callback): add error handling for callback service #73

Merged
merged 1 commit into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
evegufy marked this conversation as resolved.
Show resolved Hide resolved
*
* 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",
Dismissed Show dismissed Hide dismissed
Scope = "test",
Username = "user@name",
Dismissed Show dismissed Hide dismissed
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);
Dismissed Show dismissed Hide dismissed
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>