generated from data-altinn-no/plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Change Singleton to Transient * Update account details model Change to WhenAll for fetching account details * Remove uneccessary properties from bank models Add log text that explains which field went wrong when serializing * Refactor and enhance banking service and tests - Update `BankClient_v2.cs` to adjust JSON properties and make `CreditDebitIndicator` nullable. - Add `AccountExtensions.cs` with error logging method. - Add `HasErrors` property to `AccountV2` in `BankResponse.cs`. - Enhance `GetAccountDetailsV2` in `BankService.cs` for better error handling and logging. - Update `Altinn.Dan.Plugin.Banking.Test.csproj` to target `net8.0`, add `FakeItEasy`, and reference `Altinn.Dan.Plugin.Banking`. - Add `FakeableHttpMessageHandler.cs` for handling HTTP requests in tests and generating certificates. - Add `BankServiceTests.cs` with comprehensive unit tests for `BankService`. - Remove obsolete `UnitTest1.cs`.
- Loading branch information
Showing
8 changed files
with
470 additions
and
1,287 deletions.
There are no files selected for viewing
1,166 changes: 30 additions & 1,136 deletions
1,166
src/Altinn.Dan.Plugin.Banking/Clients/V2/BankClient_v2.cs
Large diffs are not rendered by default.
Oops, something went wrong.
36 changes: 36 additions & 0 deletions
36
src/Altinn.Dan.Plugin.Banking/Extensions/AccountExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using Altinn.Dan.Plugin.Banking.Clients.V2; | ||
using Altinn.Dan.Plugin.Banking.Services; | ||
using Microsoft.Extensions.Logging; | ||
using System; | ||
|
||
namespace Altinn.Dan.Plugin.Banking.Extensions | ||
{ | ||
public static class AccountExtensions | ||
{ | ||
public static void LogGetAccountByIdError( | ||
this Account account, | ||
ILogger logger, | ||
Exception e, | ||
BankConfig bank, | ||
Guid accountInfoRequestId) | ||
{ | ||
string correlationId = null, innerExceptionMsg = null; | ||
if (e is ApiException k) | ||
{ | ||
correlationId = k.CorrelationId; | ||
innerExceptionMsg = k.InnerException?.Message; | ||
} | ||
|
||
logger.LogError("GetAccountById failed while processing account {Account} for {Bank} ({OrgNo}) for {Subject}, error {Error}, accountInfoRequestId: {AccountInfoRequestId}, CorrelationId: {CorrelationId}, source: {source}, innerExceptionMessage: {innerExceptionMessage}", | ||
account.AccountReference, | ||
bank.Name, | ||
bank.OrgNo, | ||
account?.PrimaryOwner?.Identifier?.Value[..6], | ||
e.Message, | ||
accountInfoRequestId, | ||
correlationId, | ||
e.Source, | ||
innerExceptionMsg); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
test/Altinn.Dan.Plugin.Banking.Test/FakeableHttpMessageHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using System; | ||
using System.Net.Http; | ||
using System.Security.Cryptography.X509Certificates; | ||
using System.Security.Cryptography; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Altinn.Dan.Plugin.Banking.Test | ||
{ | ||
public abstract class FakeableHttpMessageHandler : HttpMessageHandler | ||
{ | ||
public abstract Task<HttpResponseMessage> FakeSendAsync( | ||
HttpRequestMessage request, CancellationToken cancellationToken); | ||
|
||
// sealed so FakeItEasy won't intercept calls to this method | ||
protected sealed override Task<HttpResponseMessage> SendAsync( | ||
HttpRequestMessage request, CancellationToken cancellationToken) | ||
=> this.FakeSendAsync(request, cancellationToken); | ||
} | ||
|
||
public static class CertificateGenerator | ||
{ | ||
public static X509Certificate2 GenerateSelfSignedCertificate() | ||
{ | ||
string subjectName = "Self-Signed-Cert-Example"; | ||
using var rsa = RSA.Create(2048); | ||
var certRequest = new CertificateRequest($"CN={subjectName}", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); | ||
|
||
// Add extensions to the request (just as an example) | ||
// Add keyUsage | ||
certRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, true)); | ||
X509Certificate2 generatedCert = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddYears(10)); // generate the cert and sign! | ||
|
||
X509Certificate2 pfxGeneratedCert = new X509Certificate2(generatedCert.Export(X509ContentType.Pfx)); // has to be turned into pfx or Windows at least throws a security credentials not found during sslStream.connectAsClient or HttpClient request... | ||
|
||
return pfxGeneratedCert; | ||
} | ||
} | ||
} |
267 changes: 267 additions & 0 deletions
267
test/Altinn.Dan.Plugin.Banking.Test/Services/BankServiceTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
using Altinn.ApiClients.Maskinporten.Interfaces; | ||
using Altinn.ApiClients.Maskinporten.Models; | ||
using Altinn.Dan.Plugin.Banking.Clients.V2; | ||
using Altinn.Dan.Plugin.Banking.Config; | ||
using Altinn.Dan.Plugin.Banking.Services; | ||
using FakeItEasy; | ||
using Jose; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Net.Http.Headers; | ||
using System.Security.Cryptography.X509Certificates; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Altinn.Dan.Plugin.Banking.Test.Services | ||
{ | ||
[TestClass] | ||
public class BankServiceTests | ||
{ | ||
private readonly X509Certificate2 _certificate; | ||
private readonly IOptions<ApplicationSettings> _fakeOptions; | ||
private readonly FakeableHttpMessageHandler _handler; | ||
private readonly HttpClient _client; | ||
private readonly ILoggerFactory _fakeLogger; | ||
private readonly IMaskinportenService _fakeMpService; | ||
|
||
public BankServiceTests() | ||
{ | ||
_fakeLogger = A.Fake<ILoggerFactory>(); | ||
_fakeMpService = A.Fake<IMaskinportenService>(); | ||
_fakeOptions = A.Fake<IOptions<ApplicationSettings>>(); | ||
_handler = A.Fake<FakeableHttpMessageHandler>(); | ||
_client = new HttpClient(_handler) | ||
{ | ||
BaseAddress = new Uri("http://test.com") | ||
}; | ||
|
||
A.CallTo(() => _fakeMpService.GetToken(A<string>._, A<string>._, A<string>._, A<string>._, A<string>._, A<string>._, A<bool>._)).Returns(new TokenResponse { AccessToken = "321" }); | ||
_certificate = CertificateGenerator.GenerateSelfSignedCertificate(); | ||
A.CallTo(() => _fakeOptions.Value).Returns(new ApplicationSettings { Jwk = "321", ClientId = "54345", BankScope = "somescope", OedDecryptCert = _certificate }); | ||
} | ||
|
||
[TestMethod] | ||
public async Task GetAccounts_Ok_Accounts() | ||
{ | ||
// Arrange | ||
var nin = "12345678901"; | ||
var fromDate = DateTime.Now.AddMonths(-1); | ||
var toDate = DateTime.Now; | ||
var bankName = "bank1"; | ||
var orgNumber = "789"; | ||
var bankList = GetDefaultBankConfig(bankName, orgNumber); | ||
|
||
var accounts = GetDefaultAccounts(bankName, orgNumber); | ||
var account1Details = GetAccountDetails(accounts.Accounts1.ElementAt(0)); | ||
var account2Details = GetAccountDetails(accounts.Accounts1.ElementAt(1)); | ||
FakeGetAccounts(accounts); | ||
FakeGetAccountDetails(account1Details); | ||
FakeGetAccountDetails(account2Details); | ||
|
||
var bankService = new BankService(_fakeLogger, _fakeMpService, _fakeOptions); | ||
|
||
// Act | ||
var result = await bankService.GetAccounts(nin, bankList, fromDate, toDate, Guid.NewGuid(), false); | ||
|
||
// Assert | ||
Assert.IsNotNull(result); | ||
Assert.AreEqual(1, result.BankAccounts.Count); | ||
Assert.IsFalse(result.BankAccounts.Single().HasErrors); | ||
Assert.AreEqual(2, result.BankAccounts.Single().Accounts.Count); | ||
} | ||
|
||
[TestMethod] | ||
public async Task GetAccounts_InternalServerError_BankHasErrors() | ||
{ | ||
// Arrange | ||
var nin = "12345678901"; | ||
var fromDate = DateTime.Now.AddMonths(-1); | ||
var toDate = DateTime.Now; | ||
var bankName = "bank1"; | ||
var orgNumber = "789"; | ||
var bankList = GetDefaultBankConfig(bankName, orgNumber); | ||
|
||
var accounts = GetDefaultAccounts(bankName, orgNumber); | ||
FakeGetAccounts(accounts, HttpStatusCode.InternalServerError); | ||
|
||
var bankService = new BankService(_fakeLogger, _fakeMpService, _fakeOptions); | ||
|
||
// Act | ||
var result = await bankService.GetAccounts(nin, bankList, fromDate, toDate, Guid.NewGuid(), false); | ||
|
||
// Assert | ||
Assert.IsNotNull(result); | ||
Assert.AreEqual(1, result.BankAccounts.Count); | ||
Assert.IsTrue(result.BankAccounts.Single().HasErrors); | ||
Assert.AreEqual(0, result.BankAccounts.Single().Accounts.Count); | ||
Assert.AreEqual(bankName, result.BankAccounts.Single().BankName); | ||
} | ||
|
||
[TestMethod] | ||
public async Task GetAccounts_GetAccountDetailsInternalServerError_AccountHasErrors() | ||
{ | ||
// Arrange | ||
var nin = "12345678901"; | ||
var fromDate = DateTime.Now.AddMonths(-1); | ||
var toDate = DateTime.Now; | ||
var bankName = "bank1"; | ||
var orgNumber = "789"; | ||
var bankList = GetDefaultBankConfig(bankName, orgNumber); | ||
|
||
var accounts = GetDefaultAccounts(bankName, orgNumber); | ||
accounts.Accounts1.Add(GetAccount(bankName, orgNumber, "3")); | ||
var account1Details = GetAccountDetails(accounts.Accounts1.ElementAt(0)); | ||
var account2Details = GetAccountDetails(accounts.Accounts1.ElementAt(1)); | ||
var account3Details = GetAccountDetails(accounts.Accounts1.ElementAt(2)); | ||
FakeGetAccounts(accounts); | ||
FakeGetAccountDetails(account1Details); | ||
FakeGetAccountDetails(account2Details, HttpStatusCode.InternalServerError); | ||
FakeGetAccountDetails(account3Details, HttpStatusCode.InternalServerError); | ||
|
||
var bankService = new BankService(_fakeLogger, _fakeMpService, _fakeOptions); | ||
|
||
// Act | ||
var result = await bankService.GetAccounts(nin, bankList, fromDate, toDate, Guid.NewGuid(), false); | ||
|
||
// Assert | ||
Assert.IsNotNull(result); | ||
Assert.AreEqual(1, result.BankAccounts.Count); | ||
Assert.IsFalse(result.BankAccounts.Single().HasErrors); | ||
Assert.AreEqual(2, result.BankAccounts.Single().Accounts.Count); | ||
Assert.IsTrue(result.BankAccounts.Single().Accounts.Any(x => x.HasErrors)); | ||
} | ||
|
||
private Dictionary<string, BankConfig> GetDefaultBankConfig(string bankName, string orgNumber) | ||
{ | ||
return new Dictionary<string, BankConfig> | ||
{ | ||
{ | ||
orgNumber, | ||
new BankConfig | ||
{ | ||
Name = bankName, | ||
Client = _client, | ||
MaskinportenEnv = "test1", | ||
BankAudience = "someaudience" | ||
} | ||
} | ||
}; | ||
} | ||
|
||
private void FakeGetAccountDetails(AccountDetails accountDetails, HttpStatusCode httpStatusCode = HttpStatusCode.OK) | ||
{ | ||
A.CallTo(() => _handler.FakeSendAsync(A<HttpRequestMessage>.That.Matches(x => x.RequestUri != null && x.RequestUri.AbsoluteUri.Contains($"accounts/{accountDetails.Account.AccountReference}")), A<CancellationToken>._)) | ||
.Returns(Task.FromResult(new HttpResponseMessage | ||
{ | ||
StatusCode = httpStatusCode, | ||
Content = new StringContent(JWT.Encode(JsonConvert.SerializeObject(accountDetails), _certificate.GetRSAPublicKey(), JweAlgorithm.RSA_OAEP_256, JweEncryption.A128CBC_HS256), Encoding.UTF8, new MediaTypeHeaderValue("application/json")) | ||
})); | ||
} | ||
|
||
private void FakeGetAccounts(Accounts accounts, HttpStatusCode httpStatusCode = HttpStatusCode.OK) | ||
{ | ||
A.CallTo(() => _handler.FakeSendAsync(A<HttpRequestMessage>._, A<CancellationToken>._)) | ||
.Returns(Task.FromResult(new HttpResponseMessage | ||
{ | ||
StatusCode = httpStatusCode, | ||
Content = new StringContent(JWT.Encode(JsonConvert.SerializeObject(accounts), _certificate.GetRSAPublicKey(), JweAlgorithm.RSA_OAEP_256, JweEncryption.A128CBC_HS256), Encoding.UTF8, new MediaTypeHeaderValue("application/json")) | ||
})) | ||
.NumberOfTimes(1); | ||
} | ||
|
||
private static AccountDetails GetAccountDetails(Account account) | ||
{ | ||
return new AccountDetails | ||
{ | ||
ResponseDetails = new ResponseDetails | ||
{ | ||
Message = "OK", | ||
Status = ResponseDetailsStatus.Complete, | ||
}, | ||
Account = new AccountDetail | ||
{ | ||
AccountIdentifier = account.AccountIdentifier, | ||
AccountReference = account.AccountReference, | ||
PrimaryOwner = account.PrimaryOwner, | ||
Status = account.Status, | ||
Servicer = account.Servicer, | ||
Type = account.Type, | ||
Balances = new List<Balance> | ||
{ | ||
new Balance | ||
{ | ||
CreditDebitIndicator = CreditOrDebit.Credit, | ||
CreditLineAmount = 100, | ||
CreditLineIncluded = true, | ||
Amount = 100, | ||
Currency = "NOK", | ||
Type = BalanceType.AvailableBalance | ||
}, | ||
new Balance | ||
{ | ||
CreditDebitIndicator = CreditOrDebit.Credit, | ||
CreditLineAmount = 100, | ||
CreditLineIncluded = true, | ||
Amount = 100, | ||
Currency = "NOK", | ||
Type = BalanceType.AvailableBalance | ||
} | ||
} | ||
}, | ||
}; | ||
} | ||
|
||
private static Accounts GetDefaultAccounts(string bankName, string orgNumber) | ||
{ | ||
return new Accounts | ||
{ | ||
ResponseDetails = new ResponseDetails | ||
{ | ||
Message = "OK", | ||
Status = ResponseDetailsStatus.Complete | ||
}, | ||
Accounts1 = | ||
[ | ||
GetAccount(bankName, orgNumber, "1"), | ||
GetAccount(bankName, orgNumber, "2"), | ||
] | ||
}; | ||
} | ||
|
||
private static Account GetAccount(string bankName, string orgNumber, string accRef) | ||
{ | ||
return new Account | ||
{ | ||
AccountReference = accRef, | ||
AccountIdentifier = "asdasdasd", | ||
Type = AccountType.LoanAccount, | ||
Status = AccountStatus.Enabled, | ||
Servicer = new FinancialInstitution | ||
{ | ||
Identifier = new Identifier | ||
{ | ||
Type = IdentifierType.NationalIdentityNumber, | ||
Value = orgNumber | ||
}, | ||
Name = bankName | ||
}, | ||
PrimaryOwner = new AccountRole | ||
{ | ||
Identifier = new Identifier | ||
{ | ||
Type = IdentifierType.NationalIdentityNumber, | ||
Value = "12345678910" | ||
} | ||
} | ||
}; | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.