Skip to content

Commit

Permalink
fix(callback): add error handling for callback service
Browse files Browse the repository at this point in the history
Refs: #67
  • Loading branch information
Phil91 committed Jul 31, 2024
1 parent 2853060 commit b4e4c7c
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 6 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);
}
}
197 changes: 197 additions & 0 deletions tests/processes/DimProcess.Library.Tests/CallbackServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/********************************************************************************
* 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",

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical test

The hard-coded value "passWord" flows to the
setter call argument
in
Password
.
Scope = "test",
Username = "user@name",

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical test

The hard-coded value "user@name" flows to the
setter call argument
in
Username
.
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>
35 changes: 35 additions & 0 deletions tests/shared/Tests.Shared/AutoFixtureExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/********************************************************************************
* 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;
}
}
53 changes: 53 additions & 0 deletions tests/shared/Tests.Shared/HttpMessageHandlerMock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/********************************************************************************
* 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);

Check warning

Code scanning / CodeQL

Missing Dispose call on local IDisposable Warning test

Disposable 'HttpResponseMessage' is created but not disposed.
httpResponseMessage.RequestMessage = isRequestUri ? request : null;
if (httpContent != null)
{
httpResponseMessage.Content = httpContent;
}

return Task.FromResult(httpResponseMessage);
}

public HttpRequestMessage? RequestMessage { get; private set; } = null;
}
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 b4e4c7c

Please sign in to comment.