diff --git a/src/RobinTTY.NordigenApiClient.Tests/JsonWebTokenPairTests.cs b/src/RobinTTY.NordigenApiClient.Tests/AuthenticationTests.cs
similarity index 78%
rename from src/RobinTTY.NordigenApiClient.Tests/JsonWebTokenPairTests.cs
rename to src/RobinTTY.NordigenApiClient.Tests/AuthenticationTests.cs
index 67544b7..2defee9 100644
--- a/src/RobinTTY.NordigenApiClient.Tests/JsonWebTokenPairTests.cs
+++ b/src/RobinTTY.NordigenApiClient.Tests/AuthenticationTests.cs
@@ -1,17 +1,22 @@
using Microsoft.IdentityModel.JsonWebTokens;
+using RobinTTY.NordigenApiClient.Models;
using RobinTTY.NordigenApiClient.Models.Jwt;
using RobinTTY.NordigenApiClient.Utility;
namespace RobinTTY.NordigenApiClient.Tests;
-internal class JsonWebTokenPairTests
+///
+/// Tests aspects of authentication related to the and classes.
+///
+internal class AuthenticationTests
{
- private NordigenClient _apiClient = null!;
-
- [OneTimeSetUp]
- public void Setup()
+ ///
+ /// Tests creating , passing null as an argument.
+ ///
+ [Test]
+ public void CreateCredentialsWithNull()
{
- _apiClient = TestExtensions.GetConfiguredClient();
+ Assert.Throws(() => { _ = new NordigenClientCredentials(null!, null!); });
}
///
@@ -48,22 +53,6 @@ public void CreateInvalidJsonWebTokenPair()
Assert.Throws(() => new JsonWebTokenPair(exampleToken, exampleToken));
}
- ///
- /// Tests that is populated after the first authenticated request is made.
- ///
- [Test]
- public async Task CheckValidTokensAfterRequest()
- {
- Assert.That(_apiClient.JsonWebTokenPair, Is.Null);
- await _apiClient.RequisitionsEndpoint.GetRequisitions(5, 0, CancellationToken.None);
- Assert.Multiple(() =>
- {
- Assert.That(_apiClient.JsonWebTokenPair, Is.Not.Null);
- Assert.That(_apiClient.JsonWebTokenPair!.AccessToken.EncodedToken, Has.Length.GreaterThan(0));
- Assert.That(_apiClient.JsonWebTokenPair!.RefreshToken.EncodedToken, Has.Length.GreaterThan(0));
- });
- }
-
///
/// Tests the token expiry extension method for correct behavior respecting time zones.
///
diff --git a/src/RobinTTY.NordigenApiClient.Tests/CredentialTests.cs b/src/RobinTTY.NordigenApiClient.Tests/CredentialTests.cs
deleted file mode 100644
index 9a0c2fa..0000000
--- a/src/RobinTTY.NordigenApiClient.Tests/CredentialTests.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using RobinTTY.NordigenApiClient.Models;
-using RobinTTY.NordigenApiClient.Models.Errors;
-using RobinTTY.NordigenApiClient.Models.Requests;
-
-namespace RobinTTY.NordigenApiClient.Tests;
-
-///
-/// Tests the instantiation of the .
-///
-internal class CredentialTests
-{
- private readonly string[] _secrets = File.ReadAllLines("secrets.txt");
-
- ///
- /// Tests the creation of the with invalid credentials.
- /// The credentials have the correct structure but were not issued for use.
- ///
- ///
- [Test]
- public async Task MakeRequestWithInvalidCredentials()
- {
- using var httpClient = new HttpClient();
- var invalidCredentials = new NordigenClientCredentials("01234567-89ab-cdef-0123-456789abcdef",
- "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
- var apiClient = new NordigenClient(httpClient, invalidCredentials);
-
- // Returns BasicError
- var agreementsResponse = await apiClient.AgreementsEndpoint.GetAgreements(10, 0);
- Assert.That(ErrorMatchesExpectation(agreementsResponse.Error!), Is.True);
-
- // Returns InstitutionsError
- var institutionResponse = await apiClient.InstitutionsEndpoint.GetInstitutions();
- Assert.That(ErrorMatchesExpectation(institutionResponse.Error!), Is.True);
-
- // Returns AccountsError
- var balancesResponse = await apiClient.AccountsEndpoint.GetBalances(_secrets[9]);
- Assert.Multiple(() =>
- {
- Assert.That(ErrorMatchesExpectation(balancesResponse.Error!), Is.True);
- Assert.That(
- new object?[]
- {
- balancesResponse.Error!.EndDateError, balancesResponse.Error.StartDateError,
- balancesResponse.Error.Type
- }, Has.All.Null);
- });
-
- // Returns CreateAgreementError
- var agreementRequest = new CreateAgreementRequest(90, 90,
- new List {"balances", "details", "transactions"}, "SANDBOXFINANCE_SFIN0000");
- var createAgreementResponse = await apiClient.AgreementsEndpoint.CreateAgreement(agreementRequest);
- Assert.Multiple(() =>
- {
- Assert.That(ErrorMatchesExpectation(createAgreementResponse.Error!), Is.True);
- Assert.That(new object?[]
- {
- createAgreementResponse.Error!.AccessScopeError, createAgreementResponse.Error.AccessValidForDaysError,
- createAgreementResponse.Error.AgreementError,
- createAgreementResponse.Error.InstitutionIdError, createAgreementResponse.Error.MaxHistoricalDaysError
- }, Has.All.Null);
- });
-
- // Returns CreateRequisitionError
- const string institutionId = "SANDBOXFINANCE_SFIN0000";
- var requisitionRequest =
- new CreateRequisitionRequest(new Uri("https://robintty.com"), institutionId, "some_reference", "EN");
- var requisitionResponse = await apiClient.RequisitionsEndpoint.CreateRequisition(requisitionRequest);
- Assert.Multiple(() =>
- {
- Assert.That(ErrorMatchesExpectation(requisitionResponse.Error!), Is.True);
- Assert.That(new object?[]
- {
- requisitionResponse.Error!.AgreementError, requisitionResponse.Error.InstitutionIdError,
- requisitionResponse.Error.AccountSelectionError,
- requisitionResponse.Error.RedirectError, requisitionResponse.Error.ReferenceError,
- requisitionResponse.Error.SocialSecurityNumberError, requisitionResponse.Error.UserLanguageError
- }, Has.All.Null);
- });
- }
-
- private static bool ErrorMatchesExpectation(BasicError error)
- {
- return error is {Detail: "Authentication credentials were not provided.", Summary: "Authentication failed"};
- }
-}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Endpoints/AccountsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/Endpoints/AccountsEndpointTests.cs
deleted file mode 100644
index ce2d5c9..0000000
--- a/src/RobinTTY.NordigenApiClient.Tests/Endpoints/AccountsEndpointTests.cs
+++ /dev/null
@@ -1,146 +0,0 @@
-using System.Net;
-using RobinTTY.NordigenApiClient.Models.Responses;
-
-namespace RobinTTY.NordigenApiClient.Tests.Endpoints;
-
-internal class AccountsEndpointTests
-{
- private readonly string[] _secrets = File.ReadAllLines("secrets.txt");
- private Guid _accountId;
- private NordigenClient _apiClient = null!;
-
- [OneTimeSetUp]
- public void Setup()
- {
- _accountId = Guid.Parse(_secrets[9]);
- _apiClient = TestExtensions.GetConfiguredClient();
- }
-
- ///
- /// Tests the retrieval of an account.
- ///
- ///
- [Test]
- public async Task GetAccount()
- {
- var accountResponse = await _apiClient.AccountsEndpoint.GetAccount(_accountId);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(accountResponse, HttpStatusCode.OK);
- var account = accountResponse.Result!;
- Assert.Multiple(() =>
- {
- Assert.That(account.InstitutionId, Is.EqualTo("SANDBOXFINANCE_SFIN0000"));
- Assert.That(account.Iban, Is.EqualTo("GL2010440000010445"));
- Assert.That(account.Status, Is.EqualTo(BankAccountStatus.Ready));
- });
- }
-
- ///
- /// Tests the retrieval of account balances.
- ///
- ///
- [Test]
- public async Task GetBalances()
- {
- var balancesResponse = await _apiClient.AccountsEndpoint.GetBalances(_accountId);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(balancesResponse, HttpStatusCode.OK);
- var balances = balancesResponse.Result!;
- Assert.Multiple(() =>
- {
- Assert.That(balances, Has.Count.EqualTo(2));
- Assert.That(balances.Any(balance => balance.BalanceAmount.Amount == (decimal) 1913.12), Is.True);
- Assert.That(balances.Any(balance => balance.BalanceAmount.Currency == "EUR"), Is.True);
- Assert.That(balances.All(balance => balance.BalanceType != BalanceType.Undefined));
- });
- }
-
- ///
- /// Tests the retrieval of account details.
- ///
- ///
- [Test]
- public async Task GetAccountDetails()
- {
- var detailsResponse = await _apiClient.AccountsEndpoint.GetAccountDetails(_accountId);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(detailsResponse, HttpStatusCode.OK);
- var details = detailsResponse.Result!;
- Assert.Multiple(() =>
- {
- Assert.That(details.Iban, Is.EqualTo("GL2010440000010445"));
- Assert.That(details.Name, Is.EqualTo("Main Account"));
- Assert.That(details.OwnerName, Is.EqualTo("Jane Doe"));
- Assert.That(details.CashAccountType, Is.EqualTo(CashAccountType.Current));
- });
- }
-
- ///
- /// Tests the retrieval of transactions.
- ///
- ///
- [Test]
- public async Task GetTransactions()
- {
- var transactionsResponse = await _apiClient.AccountsEndpoint.GetTransactions(_accountId);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(transactionsResponse, HttpStatusCode.OK);
- var transactions = transactionsResponse.Result!;
- Assert.Multiple(() =>
- {
- Assert.That(transactions.BookedTransactions.Any(t =>
- {
- var matchesAll = true;
- matchesAll &= t.BankTransactionCode == "PMNT";
- matchesAll &= t.DebtorAccount?.Iban == "GL2010440000010445";
- matchesAll &= t.DebtorName == "MON MOTHMA";
- matchesAll &= t.RemittanceInformationUnstructured ==
- "For the support of Restoration of the Republic foundation";
- matchesAll &= t.TransactionAmount.Amount == (decimal) 45.00;
- matchesAll &= t.TransactionAmount.Currency == "EUR";
- return matchesAll;
- }));
- Assert.That(transactions.PendingTransactions, Has.Count.GreaterThanOrEqualTo(1));
- });
- }
-
- ///
- /// Tests the retrieval of transactions within a specific time frame.
- ///
- ///
- [Test]
- public async Task GetTransactionRange()
- {
-#if NET6_0_OR_GREATER
- var startDate = new DateOnly(2022, 08, 04);
- var balancesResponse =
- await _apiClient.AccountsEndpoint.GetTransactions(_accountId, startDate, DateOnly.FromDateTime(DateTime.Now.Subtract(TimeSpan.FromHours(24))));
-#else
- var startDate = new DateTime(2022, 08, 04);
- var balancesResponse = await _apiClient.AccountsEndpoint.GetTransactions(_accountId, startDate,
- DateTime.Now.Subtract(TimeSpan.FromMinutes(1)));
-#endif
-
- TestExtensions.AssertNordigenApiResponseIsSuccessful(balancesResponse, HttpStatusCode.OK);
- Assert.That(balancesResponse.Result!.BookedTransactions, Has.Count.AtLeast(6));
- }
-
- ///
- /// Tests the retrieval of transactions within a specific time frame in the future. This should return an error.
- ///
- ///
- [Test]
- public async Task GetTransactionRangeInFuture()
- {
- var dateInFuture = DateTime.Now.AddDays(1);
-#if NET6_0_OR_GREATER
- var balancesResponse =
- await _apiClient.AccountsEndpoint.GetTransactions(_accountId, DateOnly.FromDateTime(dateInFuture), DateOnly.FromDateTime(dateInFuture.AddDays(1)));
-#else
- var balancesResponse =
- await _apiClient.AccountsEndpoint.GetTransactions(_accountId, dateInFuture, dateInFuture.AddDays(1));
-#endif
- TestExtensions.AssertNordigenApiResponseIsUnsuccessful(balancesResponse, HttpStatusCode.BadRequest);
- Assert.Multiple(() =>
- {
- Assert.That(balancesResponse.Error!.StartDateError, Is.Not.Null);
- Assert.That(balancesResponse.Error!.EndDateError, Is.Not.Null);
- });
- }
-}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/LiveApi/CredentialTests.cs b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/CredentialTests.cs
new file mode 100644
index 0000000..c81651d
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/CredentialTests.cs
@@ -0,0 +1,106 @@
+using RobinTTY.NordigenApiClient.Models;
+using RobinTTY.NordigenApiClient.Models.Jwt;
+using RobinTTY.NordigenApiClient.Tests.Shared;
+
+namespace RobinTTY.NordigenApiClient.Tests.LiveApi;
+
+public class CredentialTests
+{
+ private NordigenClient _apiClient = null!;
+
+ [OneTimeSetUp]
+ public void Setup()
+ {
+ _apiClient = TestHelpers.GetConfiguredClient();
+ }
+
+ #region RequestsWithSuccessfulResponse
+
+ ///
+ /// Tests that is populated after the first authenticated request is made.
+ ///
+ [Test]
+ public async Task CheckValidTokensAfterRequest()
+ {
+ Assert.That(_apiClient.JsonWebTokenPair, Is.Null);
+
+ await _apiClient.RequisitionsEndpoint.GetRequisitions(5, 0, CancellationToken.None);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(_apiClient.JsonWebTokenPair, Is.Not.Null);
+ Assert.That(_apiClient.JsonWebTokenPair!.AccessToken.EncodedToken, Has.Length.GreaterThan(0));
+ Assert.That(_apiClient.JsonWebTokenPair!.RefreshToken.EncodedToken, Has.Length.GreaterThan(0));
+ });
+ }
+
+ #endregion
+
+ #region RequestsWithErrors
+
+ ///
+ /// Tests the failure of authentication due to invalid credentials when trying to execute a request.
+ ///
+ [Test]
+ public async Task ExecuteRequestWithInvalidCredentials()
+ {
+ using var httpClient = new HttpClient();
+ var invalidCredentials = new NordigenClientCredentials("01234567-89ab-cdef-0123-456789abcdef",
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
+ var apiClient = new NordigenClient(httpClient, invalidCredentials);
+
+ var agreementsResponse = await apiClient.TokenEndpoint.GetTokenPair();
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(agreementsResponse, HttpStatusCode.Unauthorized);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(agreementsResponse.Error, "Authentication failed",
+ "No active account found with the given credentials");
+ });
+ }
+
+ ///
+ /// Tests the failure of authentication due to an invalid token when trying to execute a request.
+ ///
+ [Test]
+ public async Task ExecuteRequestWithUnauthorizedToken()
+ {
+ using var httpClient = new HttpClient();
+ var invalidCredentials = new NordigenClientCredentials("01234567-89ab-cdef-0123-456789abcdef",
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
+ var token = new JsonWebTokenPair(TestHelpers.Secrets[14], TestHelpers.Secrets[14]);
+ var apiClient = new NordigenClient(httpClient, invalidCredentials, token);
+
+ var response = await apiClient.InstitutionsEndpoint.GetInstitutions();
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.Unauthorized);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(response.Error, "Invalid token",
+ "Token is invalid or expired");
+ });
+ }
+
+ ///
+ /// Tries to execute a request using credentials that haven't whitelisted the used IP. This should cause an error.
+ ///
+ [Test]
+ public async Task ExecuteRequestWithUnauthorizedIp()
+ {
+ using var httpClient = new HttpClient();
+ var credentials = new NordigenClientCredentials(TestHelpers.Secrets[11], TestHelpers.Secrets[12]);
+ var apiClient = new NordigenClient(httpClient, credentials);
+
+ var externalIp = await httpClient.GetStringAsync("https://ipinfo.io/ip");
+ var response = await apiClient.RequisitionsEndpoint.GetRequisitions(5, 0, CancellationToken.None);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.Forbidden);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(response.Error, "IP address access denied",
+ $"Your IP {externalIp} isn't whitelisted to perform this action");
+ });
+ }
+
+ #endregion
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/AccountsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/AccountsEndpointTests.cs
new file mode 100644
index 0000000..273b4ac
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/AccountsEndpointTests.cs
@@ -0,0 +1,233 @@
+using RobinTTY.NordigenApiClient.Models.Responses;
+using RobinTTY.NordigenApiClient.Tests.Shared;
+
+namespace RobinTTY.NordigenApiClient.Tests.LiveApi.Endpoints;
+
+public class AccountsEndpointTests
+{
+ private Guid _accountId;
+ private Guid _nonExistingAccountId;
+ private const string InvalidGuid = "abcdefg";
+ private NordigenClient _apiClient = null!;
+
+ [OneTimeSetUp]
+ public void Setup()
+ {
+ _accountId = Guid.Parse(TestHelpers.Secrets[9]);
+ _nonExistingAccountId = Guid.Parse("f1d53c46-260d-4556-82df-4e5fed58e37c");
+ _apiClient = TestHelpers.GetConfiguredClient();
+ }
+
+ #region RequestsWithSuccessfulResponse
+
+ ///
+ /// Tests the retrieval of an account.
+ ///
+ [Test]
+ public async Task GetAccount()
+ {
+ var accountResponse = await _apiClient.AccountsEndpoint.GetAccount(_accountId);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(accountResponse, HttpStatusCode.OK);
+ var account = accountResponse.Result!;
+ Assert.Multiple(() =>
+ {
+ Assert.That(account.InstitutionId, Is.EqualTo("SANDBOXFINANCE_SFIN0000"));
+ Assert.That(account.Iban, Is.EqualTo("GL2010440000010445"));
+ Assert.That(account.Status, Is.EqualTo(BankAccountStatus.Ready));
+ });
+ }
+
+ ///
+ /// Tests the retrieval of account balances.
+ ///
+ [Test]
+ public async Task GetBalances()
+ {
+ var balancesResponse = await _apiClient.AccountsEndpoint.GetBalances(_accountId);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(balancesResponse, HttpStatusCode.OK);
+ var balances = balancesResponse.Result!;
+ Assert.Multiple(() =>
+ {
+ Assert.That(balances, Has.Count.EqualTo(2));
+ Assert.That(balances.Any(balance => balance.BalanceAmount.Amount == (decimal) 1913.12), Is.True);
+ Assert.That(balances.Any(balance => balance.BalanceAmount.Currency == "EUR"), Is.True);
+ Assert.That(balances.All(balance => balance.BalanceType != BalanceType.Undefined));
+ });
+ }
+
+ ///
+ /// Tests the retrieval of account details.
+ ///
+ [Test]
+ public async Task GetAccountDetails()
+ {
+ var detailsResponse = await _apiClient.AccountsEndpoint.GetAccountDetails(_accountId);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(detailsResponse, HttpStatusCode.OK);
+ var details = detailsResponse.Result!;
+ Assert.Multiple(() =>
+ {
+ Assert.That(details.Iban, Is.EqualTo("GL2010440000010445"));
+ Assert.That(details.Name, Is.EqualTo("Main Account"));
+ Assert.That(details.OwnerName, Is.EqualTo("Jane Doe"));
+ Assert.That(details.CashAccountType, Is.EqualTo(CashAccountType.Current));
+ });
+ }
+
+ ///
+ /// Tests the retrieval of transactions.
+ ///
+ [Test]
+ public async Task GetTransactions()
+ {
+ var transactionsResponse = await _apiClient.AccountsEndpoint.GetTransactions(_accountId);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(transactionsResponse, HttpStatusCode.OK);
+ var transactions = transactionsResponse.Result!;
+ Assert.Multiple(() =>
+ {
+ Assert.That(transactions.BookedTransactions.Any(t =>
+ {
+ var matchesAll = true;
+ matchesAll &= t.BankTransactionCode == "PMNT";
+ matchesAll &= t.DebtorAccount?.Iban == "GL2010440000010445";
+ matchesAll &= t.DebtorName == "MON MOTHMA";
+ matchesAll &= t.RemittanceInformationUnstructured ==
+ "For the support of Restoration of the Republic foundation";
+ matchesAll &= t.TransactionAmount.Amount == (decimal) 45.00;
+ matchesAll &= t.TransactionAmount.Currency == "EUR";
+ return matchesAll;
+ }));
+ Assert.That(transactions.PendingTransactions, Has.Count.GreaterThanOrEqualTo(1));
+ });
+ }
+
+ ///
+ /// Tests the retrieval of transactions within a specific time frame.
+ ///
+ [Test]
+ public async Task GetTransactionRange()
+ {
+#if NET6_0_OR_GREATER
+ var startDate = new DateOnly(2022, 08, 04);
+ var balancesResponse =
+ await _apiClient.AccountsEndpoint.GetTransactions(_accountId, startDate,
+ DateOnly.FromDateTime(DateTime.Now.Subtract(TimeSpan.FromHours(24))));
+#else
+ var startDate = new DateTime(2022, 08, 04);
+ var balancesResponse = await _apiClient.AccountsEndpoint.GetTransactions(_accountId, startDate,
+ DateTime.Now.Subtract(TimeSpan.FromDays(1)));
+#endif
+
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(balancesResponse, HttpStatusCode.OK);
+ Assert.That(balancesResponse.Result!.BookedTransactions, Has.Count.AtLeast(6));
+ }
+
+ #endregion
+
+ #region RequestsWithErrors
+
+ ///
+ /// Tests the retrieval of an account that does not exist. This should return an error.
+ ///
+ [Test]
+ public async Task GetAccountWithInvalidGuid()
+ {
+ var accountResponse = await _apiClient.AccountsEndpoint.GetAccount(InvalidGuid);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(accountResponse, HttpStatusCode.BadRequest);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(accountResponse.Error, "Invalid Account ID", $"{InvalidGuid} is not a valid Account UUID. ");
+ });
+ }
+
+ ///
+ /// Tests the retrieval of an account that does not exist. This should return an error.
+ ///
+ [Test]
+ public async Task GetAccountThatDoesNotExist()
+ {
+ var accountResponse = await _apiClient.AccountsEndpoint.GetAccount(_nonExistingAccountId);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(accountResponse, HttpStatusCode.NotFound);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(accountResponse.Error, "Not found.", "Not found.");
+ });
+ }
+
+ ///
+ /// Tests the retrieval of balances of an account that does not exist. This should return an error.
+ ///
+ [Test]
+ public async Task GetBalancesForAccountThatDoesNotExist()
+ {
+ var balancesResponse = await _apiClient.AccountsEndpoint.GetBalances(_nonExistingAccountId);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(balancesResponse, HttpStatusCode.NotFound);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(balancesResponse.Error, $"Account ID {_nonExistingAccountId} not found", "Please check whether you specified a valid Account ID");
+ });
+ }
+
+ ///
+ /// Tests the retrieval of transactions within a specific time frame in the future. This should return an error.
+ ///
+ [Test]
+ public async Task GetTransactionRangeInFuture()
+ {
+ var startDate = DateTime.Today.AddDays(1);
+ var endDate = DateTime.Today.AddMonths(1).AddDays(1);
+
+ // Returns AccountsError
+#if NET6_0_OR_GREATER
+ var transactionsResponse = await _apiClient.AccountsEndpoint.GetTransactions(TestHelpers.Secrets[9],
+ DateOnly.FromDateTime(startDate), DateOnly.FromDateTime(endDate));
+#else
+ var transactionsResponse = await _apiClient.AccountsEndpoint.GetTransactions(TestHelpers.Secrets[9],
+ startDate, endDate);
+#endif
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(transactionsResponse, HttpStatusCode.BadRequest);
+ Assert.That(transactionsResponse.Error?.StartDateError, Is.Not.Null);
+ Assert.That(transactionsResponse.Error?.EndDateError, Is.Not.Null);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(transactionsResponse.Error?.StartDateError,
+ "Date can't be in future",
+ $"'{startDate:yyyy-MM-dd}' can't be greater than {DateTime.Today:yyyy-MM-dd}. Specify correct date range");
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(transactionsResponse.Error?.EndDateError,
+ "Date can't be in future",
+ $"'{endDate:yyyy-MM-dd}' can't be greater than {DateTime.Today:yyyy-MM-dd}. Specify correct date range");
+ });
+ }
+
+ ///
+ /// Tests the retrieval of transactions within a specific time frame where the date range is incorrect, since the endDate is before the startDate. This should throw an exception.
+ ///
+ [Test]
+ public void GetTransactionRangeWithIncorrectRange()
+ {
+ var startDate = DateTime.Now.AddMonths(-1);
+ var endDateBeforeStartDate = startDate.AddDays(-1);
+
+#if NET6_0_OR_GREATER
+ var exception = Assert.ThrowsAsync(async () =>
+ await _apiClient.AccountsEndpoint.GetTransactions(_accountId, DateOnly.FromDateTime(startDate),
+ DateOnly.FromDateTime(endDateBeforeStartDate)));
+
+ Assert.That(exception.Message,
+ Is.EqualTo(
+ $"Starting date '{DateOnly.FromDateTime(startDate)}' is greater than end date '{DateOnly.FromDateTime(endDateBeforeStartDate)}'. When specifying date range, starting date must precede the end date."));
+#else
+ var exception = Assert.ThrowsAsync(async () =>
+ await _apiClient.AccountsEndpoint.GetTransactions(_accountId, startDate, endDateBeforeStartDate));
+
+ Assert.That(exception.Message,
+ Is.EqualTo(
+ $"Starting date '{startDate}' is greater than end date '{endDateBeforeStartDate}'. When specifying date range, starting date must precede the end date."));
+#endif
+ }
+
+ #endregion
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Endpoints/AgreementsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/AgreementsEndpointTests.cs
similarity index 55%
rename from src/RobinTTY.NordigenApiClient.Tests/Endpoints/AgreementsEndpointTests.cs
rename to src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/AgreementsEndpointTests.cs
index 77a0e50..6140529 100644
--- a/src/RobinTTY.NordigenApiClient.Tests/Endpoints/AgreementsEndpointTests.cs
+++ b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/AgreementsEndpointTests.cs
@@ -1,35 +1,35 @@
-using System.Net;
-using RobinTTY.NordigenApiClient.Models.Errors;
-using RobinTTY.NordigenApiClient.Models.Requests;
+using RobinTTY.NordigenApiClient.Models.Requests;
using RobinTTY.NordigenApiClient.Models.Responses;
+using RobinTTY.NordigenApiClient.Tests.Shared;
-namespace RobinTTY.NordigenApiClient.Tests.Endpoints;
+namespace RobinTTY.NordigenApiClient.Tests.LiveApi.Endpoints;
-internal class AgreementsEndpointTests
+public class AgreementsEndpointTests
{
private NordigenClient _apiClient = null!;
[OneTimeSetUp]
public void Setup()
{
- _apiClient = TestExtensions.GetConfiguredClient();
+ _apiClient = TestHelpers.GetConfiguredClient();
}
+ #region RequestsWithSuccessfulResponse
+
///
/// Tests the paging mechanism of retrieving end user agreements.
/// Creates 3 agreements, retrieves them using 3 s and deletes the agreements after.
///
- ///
[Test]
public async Task GetAgreementsPaged()
{
// Get existing agreements
var existingAgreements = await _apiClient.AgreementsEndpoint.GetAgreements(100, 0);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(existingAgreements, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(existingAgreements, HttpStatusCode.OK);
// Create 3 example agreements
var agreementRequest = new CreateAgreementRequest(90, 90,
- new List {"balances", "details", "transactions"}, "SANDBOXFINANCE_SFIN0000");
+ ["balances", "details", "transactions"], "SANDBOXFINANCE_SFIN0000");
var ids = new List();
var existingIds = existingAgreements.Result!.Results.Select(agreement => agreement.Id.ToString()).ToList();
@@ -37,21 +37,21 @@ public async Task GetAgreementsPaged()
for (var i = 0; i < 3; i++)
{
var createResponse = await _apiClient.AgreementsEndpoint.CreateAgreement(agreementRequest);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(createResponse, HttpStatusCode.Created);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(createResponse, HttpStatusCode.Created);
ids.Add(createResponse.Result!.Id.ToString());
}
// Get a response page for each agreement
var page1Response = await _apiClient.AgreementsEndpoint.GetAgreements(1, 0);
- AssertThatAgreementPageContainsAgreement(page1Response, ids);
+ AssertionHelpers.AssertThatAgreementPageContainsAgreement(page1Response, ids);
var page2Response = await page1Response.Result!.GetNextPage(_apiClient);
Assert.That(page2Response, Is.Not.Null);
- AssertThatAgreementPageContainsAgreement(page2Response!, ids);
+ AssertionHelpers.AssertThatAgreementPageContainsAgreement(page2Response!, ids);
var page3Response = await page2Response!.Result!.GetNextPage(_apiClient);
Assert.That(page3Response, Is.Not.Null);
- AssertThatAgreementPageContainsAgreement(page3Response!, ids);
+ AssertionHelpers.AssertThatAgreementPageContainsAgreement(page3Response!, ids);
// On the last page there should be a Url to the previous one
Assert.That(page3Response!.Result!.Previous, Is.Not.Null);
@@ -60,7 +60,7 @@ public async Task GetAgreementsPaged()
var previousPageResponse = await page3Response.Result!.GetPreviousPage(_apiClient);
Assert.That(previousPageResponse, Is.Not.Null);
- AssertThatAgreementPageContainsAgreement(previousPageResponse!, ids);
+ AssertionHelpers.AssertThatAgreementPageContainsAgreement(previousPageResponse!, ids);
// The previous page agreement id should equal page 2 agreement id
var prevAgreementId = previousPageResponse!.Result!.Results.First().Id;
@@ -72,61 +72,46 @@ public async Task GetAgreementsPaged()
foreach (var id in ids)
{
var result = await _apiClient.AgreementsEndpoint.DeleteAgreement(id);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(result, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(result, HttpStatusCode.OK);
}
}
///
/// Tests the retrieval of one agreement via and string id.
///
- ///
[Test]
public async Task GetAgreement()
{
// Create agreement
var agreementRequest = new CreateAgreementRequest(90, 90,
- new List {"balances", "details", "transactions"}, "SANDBOXFINANCE_SFIN0000");
+ ["balances", "details", "transactions"], "SANDBOXFINANCE_SFIN0000");
var createResponse = await _apiClient.AgreementsEndpoint.CreateAgreement(agreementRequest);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(createResponse, HttpStatusCode.Created);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(createResponse, HttpStatusCode.Created);
var id = createResponse.Result!.Id;
// Get agreement via guid and string id, should retrieve the same agreement
var agreementResponseGuid = await _apiClient.AgreementsEndpoint.GetAgreement(id);
var agreementResponseString = await _apiClient.AgreementsEndpoint.GetAgreement(id.ToString());
- TestExtensions.AssertNordigenApiResponseIsSuccessful(agreementResponseGuid, HttpStatusCode.OK);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(agreementResponseString, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(agreementResponseGuid, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(agreementResponseString, HttpStatusCode.OK);
Assert.That(agreementResponseGuid.Result!.Id, Is.EqualTo(agreementResponseString.Result!.Id));
// Delete agreement
var deleteResponse = await _apiClient.AgreementsEndpoint.DeleteAgreement(id);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(deleteResponse, HttpStatusCode.OK);
- }
-
- ///
- /// Tests the retrieval of an agreement with an invalid guid.
- ///
- ///
- [Test]
- public async Task GetAgreementWithInvalidGuid()
- {
- const string guid = "f84d7b8-dee4-4cd9-bc6d-842ef78f6028";
- var response = await _apiClient.AgreementsEndpoint.GetAgreement(guid);
- TestExtensions.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.NotFound);
- Assert.That(response.Error!.Detail, Is.EqualTo("Not found."));
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(deleteResponse, HttpStatusCode.OK);
}
///
/// Tests the creation and deletion of an end user agreement.
///
- ///
[Test]
public async Task CreateAcceptAndDeleteAgreement()
{
// Create the agreement
- var agreement = new CreateAgreementRequest(90, 90, new List {"balances", "details", "transactions"},
+ var agreement = new CreateAgreementRequest(90, 90, ["balances", "details", "transactions"],
"SANDBOXFINANCE_SFIN0000");
var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.Created);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.Created);
var result = response.Result!;
Assert.Multiple(() =>
@@ -142,53 +127,100 @@ public async Task CreateAcceptAndDeleteAgreement()
// Accept the agreement (should fail)
var acceptMetadata = new AcceptAgreementRequest("example_user_agent", "192.168.178.1");
var acceptResponse = await _apiClient.AgreementsEndpoint.AcceptAgreement(response.Result!.Id, acceptMetadata);
- TestExtensions.AssertNordigenApiResponseIsUnsuccessful(acceptResponse, HttpStatusCode.Forbidden);
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(acceptResponse, HttpStatusCode.Forbidden);
Assert.That(acceptResponse.Error!.Detail,
Is.EqualTo(
"Your company doesn't have permission to accept EUA. You'll have to use our default form for this action."));
// Delete the agreement
var deletionResponse = await _apiClient.AgreementsEndpoint.DeleteAgreement(result.Id);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(deletionResponse, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(deletionResponse, HttpStatusCode.OK);
Assert.That(deletionResponse.Result!.Summary, Is.EqualTo("End User Agreement deleted"));
}
+ #endregion
+
+ #region RequestsWithErrors
+
+ ///
+ /// Tests the retrieval of an agreement with an invalid guid.
+ ///
+ [Test]
+ public async Task GetAgreementWithInvalidGuid()
+ {
+ const string guid = "f84d7b8-dee4-4cd9-bc6d-842ef78f6028";
+
+ var response = await _apiClient.AgreementsEndpoint.GetAgreement(guid);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(response.Error, "Invalid EndUserAgreement ID",
+ $"{guid} is not a valid EndUserAgreement UUID. ");
+ });
+ }
+
///
/// Tests the creation of an end user agreement with an invalid institution id.
///
- ///
[Test]
public async Task CreateAgreementWithInvalidInstitutionId()
{
- var agreement = new CreateAgreementRequest(90, 90, new List {"balances", "details", "transactions"},
- "SANDBOXFINANCE_SFIN000");
+ var agreement = new CreateAgreementRequest(90, 90,
+ ["balances", "details", "transactions"], "invalid_institution");
+
var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement);
- TestExtensions.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
- var result = response.Error!;
- Assert.That(result.InstitutionIdError, Is.Not.Null);
- Assert.That(result.InstitutionIdError!.Detail,
- Is.EqualTo("Get Institution IDs from /institutions/?country={$COUNTRY_CODE}"));
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ Assert.Multiple(() =>
+ {
+ Assert.That(response.Error!.InstitutionIdError, Is.Not.Null);
+ Assert.That(response.Error!.InstitutionIdError!.Summary,
+ Is.EqualTo("Unknown Institution ID invalid_institution"));
+ Assert.That(response.Error!.InstitutionIdError!.Detail,
+ Is.EqualTo("Get Institution IDs from /institutions/?country={$COUNTRY_CODE}"));
+ });
+ }
+
+ ///
+ /// Tests the creation of an end user agreement with an empty institution id and empty access scopes.
+ ///
+ [Test]
+ public async Task CreateAgreementWithEmptyInstitutionIdAndAccessScopes()
+ {
+ var agreement = new CreateAgreementRequest(90, 90, null!, null!);
+
+ var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement);
+
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ Assert.Multiple(() =>
+ {
+ Assert.That(response.Error!.InstitutionIdError, Is.Not.Null);
+ Assert.That(response.Error!.InstitutionIdError!.Summary, Is.EqualTo("This field may not be null."));
+ Assert.That(response.Error!.InstitutionIdError!.Detail, Is.EqualTo("This field may not be null."));
+
+ Assert.That(response.Error!.AccessScopeError!.Summary, Is.EqualTo("This field may not be null."));
+ Assert.That(response.Error!.AccessScopeError!.Detail, Is.EqualTo("This field may not be null."));
+
+ });
}
///
/// Tests the creation of an end user agreement with invalid parameters.
///
- ///
[Test]
public async Task CreateAgreementWithInvalidParams()
{
var agreement = new CreateAgreementRequest(200, 200,
- new List {"balances", "details", "transactions", "invalid", "invalid2"}, "SANDBOXFINANCE_SFIN0000");
- var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement);
- TestExtensions.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ ["balances", "details", "transactions", "invalid", "invalid2"], "SANDBOXFINANCE_SFIN0000");
+ var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement);
var result = response.Error!;
+
Assert.Multiple(() =>
{
- Assert.That(
- new[] {result.InstitutionIdError, result.AgreementError},
- Has.All.Null);
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ Assert.That(new[] {result.InstitutionIdError, result.AgreementError}, Has.All.Null);
Assert.That(result.AccessScopeError!.Detail,
Is.EqualTo("Choose one or several from ['balances', 'details', 'transactions']"));
Assert.That(result.AccessValidForDaysError!.Detail,
@@ -199,16 +231,23 @@ public async Task CreateAgreementWithInvalidParams()
});
}
- private static void AssertThatAgreementPageContainsAgreement(
- NordigenApiResponse, BasicError> pagedResponse, List ids)
+ [Test]
+ public async Task CreateAgreementWithInvalidParamsAtPolishInstitution()
{
- TestExtensions.AssertNordigenApiResponseIsSuccessful(pagedResponse, HttpStatusCode.OK);
- var page2Result = pagedResponse.Result!;
- var page2Agreements = page2Result.Results.ToList();
+ var agreement = new CreateAgreementRequest(90, 90,
+ ["balances", "transactions"], "PKO_BPKOPLPW");
+
+ var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement);
+
Assert.Multiple(() =>
{
- Assert.That(page2Agreements, Has.Count.EqualTo(1));
- Assert.That(ids, Does.Contain(page2Agreements.First().Id.ToString()));
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ Assert.That(new[] {response.Error!.InstitutionIdError, response.Error!.AgreementError}, Has.All.Null);
+ Assert.That(response.Error!.Detail,
+ Is.EqualTo("For this institution the following scopes are required together: ['details', 'balances']"));
+ Assert.That(response.Error!.Summary, Is.EqualTo("Institution access scope dependencies error"));
});
}
+
+ #endregion
}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Endpoints/InstitutionsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/InstitutionsEndpointTests.cs
similarity index 61%
rename from src/RobinTTY.NordigenApiClient.Tests/Endpoints/InstitutionsEndpointTests.cs
rename to src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/InstitutionsEndpointTests.cs
index 162abf5..4fbfb72 100644
--- a/src/RobinTTY.NordigenApiClient.Tests/Endpoints/InstitutionsEndpointTests.cs
+++ b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/InstitutionsEndpointTests.cs
@@ -1,28 +1,29 @@
-using System.Net;
+using RobinTTY.NordigenApiClient.Tests.Shared;
-namespace RobinTTY.NordigenApiClient.Tests.Endpoints;
+namespace RobinTTY.NordigenApiClient.Tests.LiveApi.Endpoints;
-internal class InstitutionsEndpointTests
+public class InstitutionsEndpointTests
{
private NordigenClient _apiClient = null!;
[OneTimeSetUp]
public void Setup()
{
- _apiClient = TestExtensions.GetConfiguredClient();
+ _apiClient = TestHelpers.GetConfiguredClient();
}
+ #region RequestsWithSuccessfulResponse
+
///
/// Tests the retrieving of institutions for all countries and a specific country (Great Britain).
///
- ///
[Test]
public async Task GetInstitutions()
{
var response = await _apiClient.InstitutionsEndpoint.GetInstitutions();
- TestExtensions.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.OK);
var response2 = await _apiClient.InstitutionsEndpoint.GetInstitutions("GB");
- TestExtensions.AssertNordigenApiResponseIsSuccessful(response2, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(response2, HttpStatusCode.OK);
var result = response.Result!.ToList();
var result2 = response2.Result!.ToList();
@@ -38,24 +39,23 @@ public async Task GetInstitutions()
///
/// Tests the retrieving of institutions with various query parameters set.
///
- ///
[Test]
public async Task GetInstitutionsWithFlags()
{
var allFlagsSetTrue = await _apiClient.InstitutionsEndpoint.GetInstitutions("GB", true, true, true, true, true,
true, true, true, true, true, true);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(allFlagsSetTrue, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(allFlagsSetTrue, HttpStatusCode.OK);
var allFlagsSetFalse = await _apiClient.InstitutionsEndpoint.GetInstitutions("GB", false, false, false, false,
false, false, false, false, false, false, false);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(allFlagsSetFalse, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(allFlagsSetFalse, HttpStatusCode.OK);
var institutionsWithAccountSelection =
await _apiClient.InstitutionsEndpoint.GetInstitutions(accountSelectionSupported: true);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(institutionsWithAccountSelection, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(institutionsWithAccountSelection, HttpStatusCode.OK);
var institutionsWithoutAccountSelection =
await _apiClient.InstitutionsEndpoint.GetInstitutions(accountSelectionSupported: false);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(institutionsWithoutAccountSelection, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(institutionsWithoutAccountSelection, HttpStatusCode.OK);
var allInstitutions = await _apiClient.InstitutionsEndpoint.GetInstitutions();
- TestExtensions.AssertNordigenApiResponseIsSuccessful(allInstitutions, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(allInstitutions, HttpStatusCode.OK);
var allFlagsTrueResult = allFlagsSetTrue.Result!.ToList();
var withAccountSelectionResult = institutionsWithAccountSelection.Result!.ToList();
@@ -73,40 +73,59 @@ public async Task GetInstitutionsWithFlags()
});
}
+ ///
+ /// Tests the retrieving of a specific institution.
+ ///
+ [Test]
+ public async Task GetInstitution()
+ {
+ var response = await _apiClient.InstitutionsEndpoint.GetInstitution("SANDBOXFINANCE_SFIN0000");
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.OK);
+ Assert.That(response.Result!.Bic, Is.EqualTo("SFIN0000"));
+ Assert.That(response.Result!.Id, Is.EqualTo("SANDBOXFINANCE_SFIN0000"));
+ Assert.That(response.Result!.Name, Is.EqualTo("Sandbox Finance"));
+ Assert.That(response.Result!.TransactionTotalDays, Is.EqualTo(90));
+ });
+ }
+
+ #endregion
+
+ #region RequestsWithErrors
+
///
/// Tests the retrieving of institutions for a country which is not covered by the API.
///
- ///
[Test]
public async Task GetInstitutionsForNotCoveredCountry()
{
var response = await _apiClient.InstitutionsEndpoint.GetInstitutions("US");
- TestExtensions.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
Assert.Multiple(() =>
{
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
Assert.That(response.Error!.Detail, Is.EqualTo("US is not a valid choice."));
Assert.That(response.Error!.Summary, Is.EqualTo("Invalid country choice."));
});
}
///
- /// Tests the retrieving of a specific institution.
+ /// Tests the retrieving of an institution with an invalid id.
///
- ///
[Test]
- public async Task GetInstitution()
+ public async Task GetNonExistingInstitution()
{
- var response = await _apiClient.InstitutionsEndpoint.GetInstitution("SANDBOXFINANCE_SFIN0000");
- TestExtensions.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.OK);
-
- var result = response.Result!;
+ var response = await _apiClient.InstitutionsEndpoint.GetInstitution("invalid_id");
+
Assert.Multiple(() =>
{
- Assert.That(result.Bic, Is.EqualTo("SFIN0000"));
- Assert.That(result.Id, Is.EqualTo("SANDBOXFINANCE_SFIN0000"));
- Assert.That(result.Name, Is.EqualTo("Sandbox Finance"));
- Assert.That(result.TransactionTotalDays, Is.EqualTo(90));
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.NotFound);
+ Assert.That(response.Error!.Detail, Is.EqualTo("Not found."));
+ Assert.That(response.Error!.Summary, Is.EqualTo("Not found."));
});
}
+
+ #endregion
}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Endpoints/RequisitionsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/RequisitionsEndpointTests.cs
similarity index 58%
rename from src/RobinTTY.NordigenApiClient.Tests/Endpoints/RequisitionsEndpointTests.cs
rename to src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/RequisitionsEndpointTests.cs
index 9a2f590..66e64aa 100644
--- a/src/RobinTTY.NordigenApiClient.Tests/Endpoints/RequisitionsEndpointTests.cs
+++ b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/RequisitionsEndpointTests.cs
@@ -1,20 +1,21 @@
-using System.Net;
-using RobinTTY.NordigenApiClient.Models.Errors;
-using RobinTTY.NordigenApiClient.Models.Requests;
+using RobinTTY.NordigenApiClient.Models.Requests;
using RobinTTY.NordigenApiClient.Models.Responses;
+using RobinTTY.NordigenApiClient.Tests.Shared;
-namespace RobinTTY.NordigenApiClient.Tests.Endpoints;
+namespace RobinTTY.NordigenApiClient.Tests.LiveApi.Endpoints;
-internal class RequisitionsEndpointTests
+public class RequisitionsEndpointTests
{
private NordigenClient _apiClient = null!;
[OneTimeSetUp]
public void Setup()
{
- _apiClient = TestExtensions.GetConfiguredClient();
+ _apiClient = TestHelpers.GetConfiguredClient();
}
+ #region RequestsWithSuccessfulResponse
+
///
/// Tests the retrieval of all existing requisitions.
///
@@ -22,13 +23,14 @@ public void Setup()
public async Task GetRequisitions()
{
var response = await _apiClient.RequisitionsEndpoint.GetRequisitions(100, 0);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.OK);
var requisitions = response.Result?.Results.ToList();
- Assert.That(requisitions, Is.Not.Null);
- Assert.That(requisitions, Has.Count.GreaterThan(0));
+
Assert.Multiple(() =>
{
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.OK);
+ Assert.That(requisitions, Is.Not.Null);
+ Assert.That(requisitions, Has.Count.GreaterThan(0));
Assert.That(requisitions!.All(req => req.Status != RequisitionStatus.Undefined));
Assert.That(requisitions, Has.All.Matches(req => req.Id != Guid.Empty));
});
@@ -38,7 +40,6 @@ public async Task GetRequisitions()
/// Tests all methods of the requisitions endpoint.
/// Creates 3 requisitions, retrieves them using 3 s and deletes the requisitions after.
///
- ///
[Test]
public async Task GetRequisitionsPaged()
{
@@ -48,12 +49,12 @@ public async Task GetRequisitionsPaged()
var agreementRequest = new CreateAgreementRequest(90, 90,
new List {"balances", "details", "transactions"}, institutionId);
var agreementResponse = await _apiClient.AgreementsEndpoint.CreateAgreement(agreementRequest);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(agreementResponse, HttpStatusCode.Created);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(agreementResponse, HttpStatusCode.Created);
var agreementId = agreementResponse.Result!.Id;
// Get existing requisitions
var existingRequisitions = await _apiClient.RequisitionsEndpoint.GetRequisitions(100, 0);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(existingRequisitions, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(existingRequisitions, HttpStatusCode.OK);
// Create 3 example requisitions
var redirect = new Uri("https://github.com/RobinTTY/NordigenApiClient");
@@ -66,7 +67,7 @@ public async Task GetRequisitionsPaged()
var requisitionRequest =
new CreateRequisitionRequest(redirect, institutionId, $"reference_{i}", "EN", agreementId);
var createResponse = await _apiClient.RequisitionsEndpoint.CreateRequisition(requisitionRequest);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(createResponse, HttpStatusCode.Created);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(createResponse, HttpStatusCode.Created);
ids.Add(createResponse.Result!.Id.ToString());
}
@@ -99,40 +100,62 @@ public async Task GetRequisitionsPaged()
// Retrieve a single requisition via guid/string id
var requisitionResponseGuid = await _apiClient.RequisitionsEndpoint.GetRequisition(page2RequisitionId);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(requisitionResponseGuid, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(requisitionResponseGuid, HttpStatusCode.OK);
var requisitionResponseString =
await _apiClient.RequisitionsEndpoint.GetRequisition(page2RequisitionId.ToString());
- TestExtensions.AssertNordigenApiResponseIsSuccessful(requisitionResponseString, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(requisitionResponseString, HttpStatusCode.OK);
Assert.That(requisitionResponseString.Result!.Id, Is.EqualTo(requisitionResponseGuid.Result!.Id));
// Delete created resources
var agreementDeletion = await _apiClient.AgreementsEndpoint.DeleteAgreement(agreementId);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(agreementDeletion, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(agreementDeletion, HttpStatusCode.OK);
existingIds.ForEach(id => ids.Remove(id));
foreach (var id in ids)
{
var result = await _apiClient.RequisitionsEndpoint.DeleteRequisition(id);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(result, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(result, HttpStatusCode.OK);
}
}
+ private static void AssertThatRequisitionsPageContainsRequisition(
+ NordigenApiResponse, BasicResponse> pagedResponse, List ids)
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(pagedResponse, HttpStatusCode.OK);
+ var page2Result = pagedResponse.Result!;
+ var page2Requisitions = page2Result.Results.ToList();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(page2Requisitions, Has.Count.EqualTo(1));
+ Assert.That(ids, Does.Contain(page2Requisitions.First().Id.ToString()));
+ Assert.That(page2Requisitions.ToList().All(req => req.Status != RequisitionStatus.Undefined));
+ });
+ }
+
+ #endregion
+
+ #region RequestsWithErrors
+
///
/// Tests the retrieval of a requisition with an invalid guid.
///
- ///
[Test]
public async Task GetRequisitionWithInvalidGuid()
{
const string guid = "f84d7b8-dee4-4cd9-bc6d-842ef78f6028";
+
var response = await _apiClient.RequisitionsEndpoint.GetRequisition(guid);
- TestExtensions.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.NotFound);
- Assert.That(response.Error!.Detail, Is.EqualTo("Not found."));
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.NotFound);
+ Assert.That(response.Error!.Detail, Is.EqualTo("Not found."));
+ });
}
///
/// Tests the creation of an end user agreement with invalid id.
///
- ///
[Test]
public async Task CreateRequisitionWithInvalidId()
{
@@ -142,7 +165,7 @@ public async Task CreateRequisitionWithInvalidId()
new CreateRequisitionRequest(redirect, "123", "internal_reference", "EN", agreementId, null, true, true);
var response = await _apiClient.RequisitionsEndpoint.CreateRequisition(requisitionRequest);
- TestExtensions.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
Assert.Multiple(() =>
{
Assert.That(response.Error!.Summary, Is.EqualTo("Invalid ID"));
@@ -151,17 +174,50 @@ public async Task CreateRequisitionWithInvalidId()
});
}
- private static void AssertThatRequisitionsPageContainsRequisition(
- NordigenApiResponse, BasicError> pagedResponse, List ids)
+ ///
+ /// Tests the creation of an end user agreement with invalid parameters in the .
+ ///
+ [Test]
+ public async Task CreateRequisitionWithInvalidParameters()
{
- TestExtensions.AssertNordigenApiResponseIsSuccessful(pagedResponse, HttpStatusCode.OK);
- var page2Result = pagedResponse.Result!;
- var page2Requisitions = page2Result.Results.ToList();
+ var redirect = new Uri("ftp://ftp.test.com");
+ // Agreement belongs to SANDBOXFINANCE_SFIN0000
+ var agreementId = Guid.Parse("f34c3c71-4a62-4a25-b998-3f37ddce84a2");
+ var requisitionRequest =
+ new CreateRequisitionRequest(redirect, "", "", "AB", agreementId, "12345", true, true);
+
+ var response = await _apiClient.RequisitionsEndpoint.CreateRequisition(requisitionRequest);
+
Assert.Multiple(() =>
{
- Assert.That(page2Requisitions, Has.Count.EqualTo(1));
- Assert.That(ids, Does.Contain(page2Requisitions.First().Id.ToString()));
- Assert.That(page2Requisitions.ToList().All(req => req.Status != RequisitionStatus.Undefined));
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+
+ Assert.That(response.Error!.AccountSelectionError!.Summary, Is.EqualTo("Account selection not supported"));
+ Assert.That(response.Error!.AccountSelectionError!.Detail,
+ Is.EqualTo("Account selection not supported for "));
+
+ Assert.That(response.Error!.AgreementError!.Summary, Is.EqualTo("Incorrect Institution ID "));
+ Assert.That(response.Error!.AgreementError!.Detail,
+ Is.EqualTo(
+ "Provided Institution ID: '' for requisition does not match EUA institution ID 'SANDBOXFINANCE_SFIN0000'. Please provide correct institution ID: 'SANDBOXFINANCE_SFIN0000'"));
+
+ Assert.That(response.Error!.InstitutionIdError!.Summary, Is.EqualTo("This field may not be blank."));
+ Assert.That(response.Error!.InstitutionIdError!.Detail, Is.EqualTo("This field may not be blank."));
+
+ Assert.That(response.Error!.InstitutionIdError!.Summary, Is.EqualTo("This field may not be blank."));
+ Assert.That(response.Error!.InstitutionIdError!.Detail, Is.EqualTo("This field may not be blank."));
+
+ Assert.That(response.Error!.SocialSecurityNumberError!.Summary,
+ Is.EqualTo("SSN verification not supported"));
+ Assert.That(response.Error!.SocialSecurityNumberError!.Detail,
+ Is.EqualTo("SSN verification not supported for "));
+
+ Assert.That(response.Error!.UserLanguageError!.Summary,
+ Is.EqualTo("Provided user_language is invalid or not supported"));
+ Assert.That(response.Error!.UserLanguageError!.Detail,
+ Is.EqualTo("'AB' is an invalid or unsupported language"));
});
}
+
+ #endregion
}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Endpoints/TokenEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/TokenEndpointTests.cs
similarity index 66%
rename from src/RobinTTY.NordigenApiClient.Tests/Endpoints/TokenEndpointTests.cs
rename to src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/TokenEndpointTests.cs
index 5e6d0b5..16d57eb 100644
--- a/src/RobinTTY.NordigenApiClient.Tests/Endpoints/TokenEndpointTests.cs
+++ b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/TokenEndpointTests.cs
@@ -1,36 +1,56 @@
-using System.Net;
-using RobinTTY.NordigenApiClient.Models;
+using RobinTTY.NordigenApiClient.Models;
using RobinTTY.NordigenApiClient.Models.Jwt;
+using RobinTTY.NordigenApiClient.Tests.Shared;
-namespace RobinTTY.NordigenApiClient.Tests.Endpoints;
+namespace RobinTTY.NordigenApiClient.Tests.LiveApi.Endpoints;
-internal class TokenEndpointTests
+public class TokenEndpointTests
{
private NordigenClient _apiClient = null!;
[OneTimeSetUp]
public void Setup()
{
- _apiClient = TestExtensions.GetConfiguredClient();
+ _apiClient = TestHelpers.GetConfiguredClient();
}
+
+ #region RequestsWithSuccessfulResponse
///
/// Tests the retrieving and refreshing of the JWT access tokens.
///
- ///
[Test]
public async Task GetJsonWebTokenPairAndRefresh()
{
var response = await _apiClient.TokenEndpoint.GetTokenPair();
- TestExtensions.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.OK);
var response2 = await _apiClient.TokenEndpoint.RefreshAccessToken(response.Result!.RefreshToken);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(response2, HttpStatusCode.OK);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(response2, HttpStatusCode.OK);
}
+ ///
+ /// Tests using the API with an expired access token.
+ /// Requires secrets.txt to contain expired access token / valid refresh token pair.
+ ///
+ [Test]
+ public async Task ReuseExpiredToken()
+ {
+ var httpClient = new HttpClient();
+ var credentials = new NordigenClientCredentials(TestHelpers.Secrets[0], TestHelpers.Secrets[1]);
+ var tokenPair = new JsonWebTokenPair(TestHelpers.Secrets[6], TestHelpers.Secrets[7]);
+ var apiClient = new NordigenClient(httpClient, credentials, tokenPair);
+
+ var result = await apiClient.RequisitionsEndpoint.GetRequisitions(10, 0);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(result, HttpStatusCode.OK);
+ }
+
+ #endregion
+
+ #region RequestsWithErrors
+
///
/// Tests retrieving a token with invalid credentials.
///
- ///
[Test]
public async Task GetTokenWithInvalidCredentials()
{
@@ -47,26 +67,6 @@ public async Task GetTokenWithInvalidCredentials()
Assert.That(response.Error, Is.Not.Null);
});
}
-
- ///
- /// Tests using the API with an expired access token.
- /// Requires secrets.txt to contain expired access token / valid refresh token pair.
- ///
- ///
- [Test]
- public async Task ReuseExpiredToken()
- {
-#if NET6_0_OR_GREATER
- var secrets = await File.ReadAllLinesAsync("secrets.txt");
-#else
- var secrets = File.ReadAllLines("secrets.txt");
-#endif
- var httpClient = new HttpClient();
- var credentials = new NordigenClientCredentials(secrets[0], secrets[1]);
- var tokenPair = new JsonWebTokenPair(secrets[6], secrets[7]);
- var apiClient = new NordigenClient(httpClient, credentials, tokenPair);
-
- var result = await apiClient.RequisitionsEndpoint.GetRequisitions(10, 0);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(result, HttpStatusCode.OK);
- }
+
+ #endregion
}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/LiveApi/NordigenApiClientTests.cs b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/NordigenApiClientTests.cs
new file mode 100644
index 0000000..c2ff739
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/NordigenApiClientTests.cs
@@ -0,0 +1,74 @@
+using System.Collections.Concurrent;
+using RobinTTY.NordigenApiClient.Models.Errors;
+using RobinTTY.NordigenApiClient.Models.Responses;
+using RobinTTY.NordigenApiClient.Tests.Shared;
+
+namespace RobinTTY.NordigenApiClient.Tests.LiveApi;
+
+public class NordigenApiClientTests
+{
+ #region RequestsWithSuccessfulResponse
+
+ ///
+ /// Executes a request to the Nordigen API using the default base address.
+ ///
+ [Test]
+ public async Task ExecuteRequestWithDefaultBaseAddress()
+ {
+ var apiClient = TestHelpers.GetConfiguredClient();
+ await ExecuteExampleRequest(apiClient);
+ }
+
+ ///
+ /// Executes a request to the Nordigen API using a custom base address.
+ ///
+ [Test]
+ public async Task ExecuteRequestWithCustomBaseAddress()
+ {
+ var apiClient = TestHelpers.GetConfiguredClient("https://ob.gocardless.com/api/v2/");
+ await ExecuteExampleRequest(apiClient);
+ }
+
+ private async Task ExecuteExampleRequest(NordigenClient apiClient)
+ {
+ var response = await apiClient.TokenEndpoint.GetTokenPair();
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.OK);
+ var response2 = await apiClient.TokenEndpoint.RefreshAccessToken(response.Result!.RefreshToken);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(response2, HttpStatusCode.OK);
+ }
+
+ #endregion
+
+ #region RequestsWithErrors
+
+ [Test]
+ [Ignore("Test executes a lot of requests using the LiveAPI.")]
+ public async Task ExecuteRequestsUntilRateLimitReached()
+ {
+ var apiClient = TestHelpers.GetConfiguredClient();
+ NordigenApiResponse, BasicResponse>? unsuccessfulRequest = null;
+
+ while (unsuccessfulRequest is null)
+ {
+ var tasks = new ConcurrentBag, BasicResponse>>>();
+ Parallel.For(0, 10, _ =>
+ {
+ var task = apiClient.InstitutionsEndpoint.GetInstitutions("LI");
+ tasks.Add(task);
+ });
+
+ var results = await Task.WhenAll(tasks);
+ unsuccessfulRequest = results.FirstOrDefault(result => !result.IsSuccess);
+ }
+
+#if NET6_0_OR_GREATER
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(unsuccessfulRequest, HttpStatusCode.TooManyRequests);
+#else
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(unsuccessfulRequest, (HttpStatusCode)429);
+#endif
+ Assert.That(unsuccessfulRequest.Error!.Summary, Is.EqualTo("Rate limit exceeded"));
+ Assert.That(unsuccessfulRequest.Error!.Detail, Does.Match("The rate limit for this resource is [0-9]*\\/\\w*\\. Please try again in [0-9]* \\w*"));
+ }
+
+ #endregion
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Mocks/CredentialTests.cs b/src/RobinTTY.NordigenApiClient.Tests/Mocks/CredentialTests.cs
new file mode 100644
index 0000000..0c46be9
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/Mocks/CredentialTests.cs
@@ -0,0 +1,73 @@
+using RobinTTY.NordigenApiClient.Tests.Shared;
+
+namespace RobinTTY.NordigenApiClient.Tests.Mocks;
+
+public class CredentialTests
+{
+ #region RequestsWithSuccessfulResponse
+
+ ///
+ /// Tests that is populated after the first authenticated request is made.
+ ///
+ [Test]
+ public async Task CheckValidTokensAfterRequest()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.RequisitionsEndpointMockData.GetRequisitions, HttpStatusCode.OK);
+ Assert.That(apiClient.JsonWebTokenPair, Is.Null);
+
+ await apiClient.RequisitionsEndpoint.GetRequisitions(5, 0, CancellationToken.None);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(apiClient.JsonWebTokenPair, Is.Not.Null);
+ Assert.That(apiClient.JsonWebTokenPair!.AccessToken.EncodedToken, Has.Length.GreaterThan(0));
+ Assert.That(apiClient.JsonWebTokenPair!.RefreshToken.EncodedToken, Has.Length.GreaterThan(0));
+ });
+ }
+
+ #endregion
+
+ #region RequestsWithErrors
+
+ ///
+ /// Tests the failure of authentication when trying to execute a request.
+ ///
+ [Test]
+ public async Task ExecuteRequestWithInvalidCredentials()
+ {
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.CredentialMockData.NoAccountForGivenCredentialsError,
+ HttpStatusCode.Unauthorized, addDefaultAuthToken: false);
+
+ var tokenPairResponse = await apiClient.TokenEndpoint.GetTokenPair();
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(tokenPairResponse, HttpStatusCode.Unauthorized);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(tokenPairResponse.Error, "Authentication failed",
+ "No active account found with the given credentials");
+ });
+ }
+
+ ///
+ /// Tries to execute a request using credentials that haven't whitelisted the used IP. This should cause an error.
+ ///
+ [Test]
+ public async Task ExecuteRequestWithUnauthorizedIp()
+ {
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.CredentialMockData.IpNotWhitelistedError,
+ HttpStatusCode.Forbidden);
+
+ var response = await apiClient.RequisitionsEndpoint.GetRequisitions(5, 0, CancellationToken.None);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.Forbidden);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(response.Error, "IP address access denied",
+ $"Your IP 127.0.0.1 isn't whitelisted to perform this action");
+ });
+ }
+
+ #endregion
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/AccountsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/AccountsEndpointTests.cs
new file mode 100644
index 0000000..06041ec
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/AccountsEndpointTests.cs
@@ -0,0 +1,261 @@
+using FakeItEasy;
+using RobinTTY.NordigenApiClient.Models.Responses;
+using RobinTTY.NordigenApiClient.Tests.Shared;
+
+namespace RobinTTY.NordigenApiClient.Tests.Mocks.Endpoints;
+
+public class AccountsEndpointTests
+{
+ private const string InvalidGuid = "abcdefg";
+
+ #region RequestsWithSuccessfulResponse
+
+ ///
+ /// Tests the retrieval of an account.
+ ///
+ [Test]
+ public async Task GetAccount()
+ {
+ var apiClient =
+ TestHelpers.GetMockClient(TestHelpers.MockData.AccountsEndpointMockData.GetAccount, HttpStatusCode.OK);
+
+ var accountResponse = await apiClient.AccountsEndpoint.GetAccount(A.Dummy());
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(accountResponse, HttpStatusCode.OK);
+ var account = accountResponse.Result!;
+ Assert.Multiple(() =>
+ {
+ Assert.That(account.InstitutionId, Is.EqualTo("SANDBOXFINANCE_SFIN0000"));
+ Assert.That(account.Iban, Is.EqualTo("GL2010440000010445"));
+ Assert.That(account.Status, Is.EqualTo(BankAccountStatus.Ready));
+ });
+ }
+
+ ///
+ /// Tests the retrieval of account balances.
+ ///
+ [Test]
+ public async Task GetBalances()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.AccountsEndpointMockData.GetBalances,
+ HttpStatusCode.OK);
+
+ var balancesResponse = await apiClient.AccountsEndpoint.GetBalances(A.Dummy());
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(balancesResponse, HttpStatusCode.OK);
+ var balances = balancesResponse.Result!;
+ Assert.Multiple(() =>
+ {
+ Assert.That(balances, Has.Count.EqualTo(2));
+ Assert.That(balances.Any(balance => balance.BalanceAmount.Amount == (decimal) 1913.12), Is.True);
+ Assert.That(balances.Any(balance => balance.BalanceAmount.Currency == "EUR"), Is.True);
+ Assert.That(balances.All(balance => balance.BalanceType != BalanceType.Undefined));
+ });
+ }
+
+ ///
+ /// Tests the retrieval of account details.
+ ///
+ [Test]
+ public async Task GetAccountDetails()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.AccountsEndpointMockData.GetAccountDetails,
+ HttpStatusCode.OK);
+
+ var detailsResponse = await apiClient.AccountsEndpoint.GetAccountDetails(A.Dummy());
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(detailsResponse, HttpStatusCode.OK);
+ var details = detailsResponse.Result!;
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(details.Iban, Is.EqualTo("GL2010440000010445"));
+ Assert.That(details.Name, Is.EqualTo("Main Account"));
+ Assert.That(details.Product, Is.EqualTo("Credit Card"));
+ Assert.That(details.OwnerName, Is.EqualTo("Jane Doe"));
+ Assert.That(details.ResourceId, Is.EqualTo("abc"));
+ Assert.That(details.Currency, Is.EqualTo("EUR"));
+ Assert.That(details.CashAccountType, Is.EqualTo(CashAccountType.Current));
+ });
+ }
+
+ ///
+ /// Tests the retrieval of transactions.
+ ///
+ [Test]
+ public async Task GetTransactions()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.AccountsEndpointMockData.GetTransactions,
+ HttpStatusCode.OK);
+
+ var transactionsResponse = await apiClient.AccountsEndpoint.GetTransactions(A.Dummy());
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(transactionsResponse, HttpStatusCode.OK);
+ var transactions = transactionsResponse.Result!;
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(transactions.BookedTransactions.Any(t =>
+ {
+ var matchesAll = true;
+ matchesAll &= t.BankTransactionCode == "PMNT";
+ matchesAll &= t.DebtorAccount?.Iban == "GL2010440000010445";
+ matchesAll &= t.DebtorName == "MON MOTHMA";
+ matchesAll &= t.RemittanceInformationUnstructured ==
+ "For the support of Restoration of the Republic foundation";
+ matchesAll &= t.TransactionAmount.Amount == (decimal) 45.00;
+ matchesAll &= t.TransactionAmount.Currency == "EUR";
+ return matchesAll;
+ }));
+ Assert.That(transactions.PendingTransactions, Has.Count.GreaterThanOrEqualTo(1));
+ });
+ }
+
+ ///
+ /// Tests the retrieval of transactions within a specific time frame.
+ ///
+ [Test]
+ public async Task GetTransactionRange()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.AccountsEndpointMockData.GetTransactionRange,
+ HttpStatusCode.OK);
+
+#if NET6_0_OR_GREATER
+ var startDate = new DateOnly(2022, 08, 04);
+ var balancesResponse =
+ await apiClient.AccountsEndpoint.GetTransactions(A.Dummy(), startDate,
+ DateOnly.FromDateTime(DateTime.Now.Subtract(TimeSpan.FromHours(24))));
+#else
+ var startDate = new DateTime(2022, 08, 04);
+ var balancesResponse = await apiClient.AccountsEndpoint.GetTransactions(A.Dummy(), startDate,
+ DateTime.Now.Subtract(TimeSpan.FromDays(1)));
+#endif
+
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(balancesResponse, HttpStatusCode.OK);
+ Assert.That(balancesResponse.Result!.BookedTransactions, Has.Count.EqualTo(2));
+ }
+
+ #endregion
+
+ #region RequestsWithErrors
+
+ ///
+ /// Tests the retrieval of an account that does not exist. This should return an error.
+ ///
+ [Test]
+ public async Task GetAccountWithInvalidGuid()
+ {
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.AccountsEndpointMockData.GetAccountWithInvalidGuid, HttpStatusCode.BadRequest);
+
+ var accountResponse = await apiClient.AccountsEndpoint.GetAccount(InvalidGuid);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(accountResponse, HttpStatusCode.BadRequest);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(accountResponse.Error, "Invalid Account ID",
+ $"{InvalidGuid} is not a valid Account UUID. ");
+ });
+ }
+
+ ///
+ /// Tests the retrieval of an account that does not exist. This should return an error.
+ ///
+ [Test]
+ public async Task GetAccountThatDoesNotExist()
+ {
+ var apiClient =
+ TestHelpers.GetMockClient(TestHelpers.MockData.AccountsEndpointMockData.GetAccountThatDoesNotExist,
+ HttpStatusCode.NotFound);
+
+ var accountResponse = await apiClient.AccountsEndpoint.GetAccount(A.Dummy());
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(accountResponse, HttpStatusCode.NotFound);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(accountResponse.Error, "Not found.", "Not found.");
+ });
+ }
+
+ ///
+ /// Tests the retrieval of balances of an account that does not exist. This should return an error.
+ ///
+ [Test]
+ public async Task GetBalancesForAccountThatDoesNotExist()
+ {
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.AccountsEndpointMockData.GetBalancesForAccountThatDoesNotExist,
+ HttpStatusCode.NotFound);
+
+ var nonExistingAccountId = Guid.Parse("f1d53c46-260d-4556-82df-4e5fed58e37c");
+ var balancesResponse = await apiClient.AccountsEndpoint.GetBalances(A.Dummy());
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(balancesResponse, HttpStatusCode.NotFound);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(balancesResponse.Error,
+ $"Account ID {nonExistingAccountId} not found",
+ "Please check whether you specified a valid Account ID");
+ });
+ }
+
+ ///
+ /// Tests the retrieval of transactions within a specific time frame in the future. This should return an error.
+ ///
+ [Test]
+ public async Task GetTransactionRangeInFuture()
+ {
+ var startDate = new DateTime(year: 2024, month: 4, day: 21);
+ var endDate = new DateTime(year: 2024, month: 5, day: 21);
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.AccountsEndpointMockData.GetTransactionRangeInFuture, HttpStatusCode.BadRequest);
+
+ // Returns AccountsError
+#if NET6_0_OR_GREATER
+ var transactionsResponse = await apiClient.AccountsEndpoint.GetTransactions(A.Dummy(),
+ DateOnly.FromDateTime(startDate), DateOnly.FromDateTime(endDate));
+#else
+ var transactionsResponse = await apiClient.AccountsEndpoint.GetTransactions(A.Dummy(),
+ startDate, endDate);
+#endif
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(transactionsResponse, HttpStatusCode.BadRequest);
+ Assert.That(transactionsResponse.Error?.StartDateError, Is.Not.Null);
+ Assert.That(transactionsResponse.Error?.EndDateError, Is.Not.Null);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(transactionsResponse.Error?.StartDateError,
+ "Date can't be in future",
+ "'2024-04-21' can't be greater than 2024-04-20. Specify correct date range");
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(transactionsResponse.Error?.EndDateError,
+ "Date can't be in future",
+ "'2024-05-21' can't be greater than 2024-04-20. Specify correct date range");
+ });
+ }
+
+ ///
+ /// Tests the retrieval of transactions within a specific time frame where the date range is incorrect, since the endDate is before the startDate. This should throw an exception.
+ ///
+ [Test]
+ public void GetTransactionRangeWithIncorrectRange()
+ {
+ var apiClient = TestHelpers.GetMockClient(null!, HttpStatusCode.BadRequest);
+ var startDate = DateTime.Now.AddMonths(-1);
+ var endDateBeforeStartDate = startDate.AddDays(-1);
+
+#if NET6_0_OR_GREATER
+ var exception = Assert.ThrowsAsync(async () =>
+ await apiClient.AccountsEndpoint.GetTransactions(A.Dummy(), DateOnly.FromDateTime(startDate),
+ DateOnly.FromDateTime(endDateBeforeStartDate)));
+
+ Assert.That(exception.Message,
+ Is.EqualTo(
+ $"Starting date '{DateOnly.FromDateTime(startDate)}' is greater than end date '{DateOnly.FromDateTime(endDateBeforeStartDate)}'. When specifying date range, starting date must precede the end date."));
+#else
+ var exception = Assert.ThrowsAsync(async () =>
+ await apiClient.AccountsEndpoint.GetTransactions(A.Dummy(), startDate, endDateBeforeStartDate));
+
+ Assert.That(exception.Message,
+ Is.EqualTo(
+ $"Starting date '{startDate}' is greater than end date '{endDateBeforeStartDate}'. When specifying date range, starting date must precede the end date."));
+#endif
+ }
+
+ #endregion
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/AgreementsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/AgreementsEndpointTests.cs
new file mode 100644
index 0000000..7da91a8
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/AgreementsEndpointTests.cs
@@ -0,0 +1,249 @@
+using FakeItEasy;
+using RobinTTY.NordigenApiClient.Models.Requests;
+using RobinTTY.NordigenApiClient.Tests.Shared;
+
+namespace RobinTTY.NordigenApiClient.Tests.Mocks.Endpoints;
+
+public class AgreementsEndpointTests
+{
+ #region RequestsWithSuccessfulResponse
+
+ ///
+ /// Tests the retrieval of end user agreements.
+ ///
+ [Test]
+ public async Task GetAgreements()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.AgreementsEndpointMockData.GetAgreements,
+ HttpStatusCode.OK);
+
+ var agreements = await apiClient.AgreementsEndpoint.GetAgreements(100, 0);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(agreements, HttpStatusCode.OK);
+
+ var responseAgreement = agreements.Result!.Results.First();
+ Assert.Multiple(() =>
+ {
+ Assert.That(agreements.Result!.Count, Is.EqualTo(1));
+ Assert.That(agreements.Result!.Next,
+ Is.EqualTo(new Uri(
+ "https://bankaccountdata.gocardless.com/api/v2/agreements/enduser/?limit=100&offset=0")));
+ Assert.That(agreements.Result!.Previous,
+ Is.EqualTo(new Uri(
+ "https://bankaccountdata.gocardless.com/api/v2/agreements/enduser/?limit=100&offset=0")));
+ Assert.That(agreements.Result!.Results.Count(), Is.EqualTo(1));
+
+ Assert.That(responseAgreement.Id, Is.EqualTo(new Guid("3fa85f64-5717-4562-b3fc-2c963f66afa6")));
+ Assert.That(responseAgreement.Created.ToUniversalTime(),
+ Is.EqualTo(DateTime.Parse("2024-04-08T20:57:00.550Z").ToUniversalTime()));
+ Assert.That(((DateTime) responseAgreement.Accepted!).ToUniversalTime(),
+ Is.EqualTo(DateTime.Parse("2024-04-08T20:57:00.550Z").ToUniversalTime()));
+ Assert.That(responseAgreement.InstitutionId, Is.EqualTo("SANDBOXFINANCE_SFIN0000"));
+ Assert.That(responseAgreement.MaxHistoricalDays, Is.EqualTo(90));
+ Assert.That(responseAgreement.AccessValidForDays, Is.EqualTo(90));
+ Assert.That(responseAgreement.AccessScope, Has.Count.EqualTo(3));
+
+ var expectedAccessScopes = new[] {"balances", "details", "transactions"};
+ Assert.That(responseAgreement.AccessScope, Is.EqualTo(expectedAccessScopes));
+ });
+ }
+
+ ///
+ /// Tests the retrieval of an end user agreement by id.
+ ///
+ [Test]
+ public async Task GetAgreement()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.AgreementsEndpointMockData.GetAgreement,
+ HttpStatusCode.OK);
+
+ var agreement = await apiClient.AgreementsEndpoint.GetAgreement(A.Dummy());
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(agreement, HttpStatusCode.OK);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(agreement.Result!.Id, Is.EqualTo(new Guid("3fa85f64-5717-4562-b3fc-2c963f66afa6")));
+ Assert.That(agreement.Result!.InstitutionId, Is.EqualTo("SANDBOXFINANCE_SFIN0000"));
+ Assert.That(agreement.Result!.MaxHistoricalDays, Is.EqualTo(90));
+ Assert.That(agreement.Result!.AccessValidForDays, Is.EqualTo(90));
+ Assert.That(agreement.Result!.Created.ToUniversalTime(),
+ Is.EqualTo(DateTime.Parse("2024-04-08T22:54:54.869Z").ToUniversalTime()));
+ Assert.That(((DateTime) agreement.Result!.Accepted!).ToUniversalTime(),
+ Is.EqualTo(DateTime.Parse("2024-04-08T22:54:54.869Z").ToUniversalTime()));
+ var expectedAccessScopes = new[] {"balances", "details", "transactions"};
+ Assert.That(agreement.Result!.AccessScope, Is.EqualTo(expectedAccessScopes));
+ });
+ }
+
+ ///
+ /// Tests the creation of end user agreements.
+ ///
+ [Test]
+ public async Task CreateAgreement()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.AgreementsEndpointMockData.CreateAgreement,
+ HttpStatusCode.Created);
+
+ var agreementRequest = new CreateAgreementRequest(145, 145,
+ ["balances", "details", "transactions"], "SANDBOXFINANCE_SFIN0000");
+ var createResponse = await apiClient.AgreementsEndpoint.CreateAgreement(agreementRequest);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(createResponse, HttpStatusCode.Created);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(createResponse.Result!.Id, Is.EqualTo(new Guid("3fa85f64-5717-4562-b3fc-2c963f66afa7")));
+ Assert.That(createResponse.Result!.InstitutionId, Is.EqualTo("SANDBOXFINANCE_SFIN0000"));
+ Assert.That(createResponse.Result!.MaxHistoricalDays, Is.EqualTo(145));
+ Assert.That(createResponse.Result!.AccessValidForDays, Is.EqualTo(145));
+
+ var expectedAccessScopes = new[] {"balances", "details", "transactions"};
+ Assert.That(createResponse.Result!.AccessScope, Is.EqualTo(expectedAccessScopes));
+ });
+ }
+
+ ///
+ /// Tests the creation of end user agreements.
+ ///
+ [Test]
+ public async Task DeleteAgreement()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.AgreementsEndpointMockData.DeleteAgreement,
+ HttpStatusCode.OK);
+
+ var result = await apiClient.AgreementsEndpoint.DeleteAgreement(A.Dummy());
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(result, HttpStatusCode.OK);
+ Assert.Multiple(() =>
+ {
+ Assert.That(result.Result?.Summary, Is.EqualTo("End User Agreement deleted"));
+ Assert.That(result.Result?.Detail,
+ Is.EqualTo("End User Agreement bb37bc52-5b1d-44f9-b1cd-ec9594f25387 deleted"));
+ });
+ }
+
+ #endregion
+
+ #region RequestsWithErrors
+
+ ///
+ /// Tests the retrieval of an agreement with an invalid guid.
+ ///
+ [Test]
+ public async Task GetAgreementWithInvalidGuid()
+ {
+ const string guid = "f84d7b8-dee4-4cd9-bc6d-842ef78f6028";
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.AgreementsEndpointMockData.GetAgreementWithInvalidGuid,
+ HttpStatusCode.BadRequest);
+
+ var response = await apiClient.AgreementsEndpoint.GetAgreement(guid);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ AssertionHelpers.AssertBasicResponseMatchesExpectations(response.Error, "Invalid EndUserAgreement ID",
+ $"{guid} is not a valid EndUserAgreement UUID. ");
+ });
+ }
+
+ ///
+ /// Tests the creation of an end user agreement with an invalid institution id.
+ ///
+ [Test]
+ public async Task CreateAgreementWithInvalidInstitutionId()
+ {
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.AgreementsEndpointMockData.CreateAgreementWithInvalidInstitutionId,
+ HttpStatusCode.BadRequest);
+ var agreement = new CreateAgreementRequest(90, 90,
+ ["balances", "details", "transactions"], "invalid_institution");
+
+ var response = await apiClient.AgreementsEndpoint.CreateAgreement(agreement);
+
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ Assert.Multiple(() =>
+ {
+ Assert.That(response.Error!.InstitutionIdError, Is.Not.Null);
+ Assert.That(response.Error!.InstitutionIdError!.Summary,
+ Is.EqualTo("Unknown Institution ID invalid_institution"));
+ Assert.That(response.Error!.InstitutionIdError!.Detail,
+ Is.EqualTo("Get Institution IDs from /institutions/?country={$COUNTRY_CODE}"));
+ });
+ }
+
+ ///
+ /// Tests the creation of an end user agreement with an empty institution id and empty access scopes.
+ ///
+ [Test]
+ public async Task CreateAgreementWithEmptyInstitutionIdAndAccessScopes()
+ {
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.AgreementsEndpointMockData.CreateAgreementWithEmptyInstitutionIdAndAccessScopes,
+ HttpStatusCode.BadRequest);
+ var agreement = new CreateAgreementRequest(90, 90, null!, null!);
+
+ var response = await apiClient.AgreementsEndpoint.CreateAgreement(agreement);
+
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ Assert.Multiple(() =>
+ {
+ Assert.That(response.Error!.InstitutionIdError, Is.Not.Null);
+ Assert.That(response.Error!.InstitutionIdError!.Summary, Is.EqualTo("This field may not be null."));
+ Assert.That(response.Error!.InstitutionIdError!.Detail, Is.EqualTo("This field may not be null."));
+
+ Assert.That(response.Error!.AccessScopeError!.Summary, Is.EqualTo("This field may not be null."));
+ Assert.That(response.Error!.AccessScopeError!.Detail, Is.EqualTo("This field may not be null."));
+
+ });
+ }
+
+ ///
+ /// Tests the creation of an end user agreement with invalid parameters.
+ ///
+ [Test]
+ public async Task CreateAgreementWithInvalidParams()
+ {
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.AgreementsEndpointMockData.CreateAgreementWithInvalidParams,
+ HttpStatusCode.BadRequest);
+ var agreement = new CreateAgreementRequest(200, 200,
+ ["balances", "details", "transactions", "invalid", "invalid2"], "SANDBOXFINANCE_SFIN0000");
+
+ var response = await apiClient.AgreementsEndpoint.CreateAgreement(agreement);
+ var result = response.Error!;
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ Assert.That(new[] {result.InstitutionIdError, result.AgreementError}, Has.All.Null);
+ Assert.That(result.AccessScopeError!.Detail,
+ Is.EqualTo("Choose one or several from ['balances', 'details', 'transactions']"));
+ Assert.That(result.AccessValidForDaysError!.Detail,
+ Is.EqualTo("access_valid_for_days must be > 0 and <= 180"));
+ Assert.That(result.MaxHistoricalDaysError!.Detail,
+ Is.EqualTo(
+ "max_historical_days must be > 0 and <= SANDBOXFINANCE_SFIN0000 transaction_total_days (90)"));
+ });
+ }
+
+ [Test]
+ public async Task CreateAgreementWithInvalidParamsAtPolishInstitution()
+ {
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.AgreementsEndpointMockData.CreateAgreementWithInvalidParamsAtPolishInstitution,
+ HttpStatusCode.BadRequest);
+ var agreement = new CreateAgreementRequest(90, 90,
+ ["balances", "transactions"], "PKO_BPKOPLPW");
+
+ var response = await apiClient.AgreementsEndpoint.CreateAgreement(agreement);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ Assert.That(new[] {response.Error!.InstitutionIdError, response.Error!.AgreementError}, Has.All.Null);
+ Assert.That(response.Error!.Detail,
+ Is.EqualTo("For this institution the following scopes are required together: ['details', 'balances']"));
+ Assert.That(response.Error!.Summary, Is.EqualTo("Institution access scope dependencies error"));
+ });
+ }
+
+ #endregion
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/InstitutionsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/InstitutionsEndpointTests.cs
new file mode 100644
index 0000000..fe45c75
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/InstitutionsEndpointTests.cs
@@ -0,0 +1,110 @@
+using RobinTTY.NordigenApiClient.Models.Responses;
+using RobinTTY.NordigenApiClient.Tests.Shared;
+
+namespace RobinTTY.NordigenApiClient.Tests.Mocks.Endpoints;
+
+public class InstitutionsEndpointTests
+{
+ #region RequestsWithSuccessfulResponse
+
+ ///
+ /// Tests the retrieving of institutions for all countries and a specific country (Great Britain).
+ ///
+ [Test]
+ public async Task GetInstitutions()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.InstitutionsEndpointMockData.GetInstitutions,
+ HttpStatusCode.OK);
+
+ var institutions = await apiClient.InstitutionsEndpoint.GetInstitutions();
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(institutions, HttpStatusCode.OK);
+ Assert.That(institutions.Result!, Has.Count.EqualTo(2));
+ });
+ }
+
+ ///
+ /// Tests the retrieving of a specific institution.
+ ///
+ [Test]
+ public async Task GetInstitution()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.InstitutionsEndpointMockData.GetInstitution,
+ HttpStatusCode.OK);
+
+ var institution = await apiClient.InstitutionsEndpoint.GetInstitution("N26_NTSBDEB1");
+
+ var expectedSupportedFeatures = new[]
+ {
+ "account_selection",
+ "business_accounts",
+ "card_accounts",
+ "payments",
+ "private_accounts"
+ };
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(institution, HttpStatusCode.OK);
+ Assert.That(institution.Result!.Bic, Is.EqualTo("NTSBDEB1"));
+ Assert.That(institution.Result!.Id, Is.EqualTo("N26_NTSBDEB1"));
+ Assert.That(institution.Result!.Name, Is.EqualTo("N26 Bank"));
+ Assert.That(institution.Result!.TransactionTotalDays, Is.EqualTo(90));
+
+ Assert.That(institution.Result!.SupportedPayments?.SinglePayment,
+ Contains.Item(PaymentProduct.SepaCreditTransfers));
+ Assert.That(institution.Result!.SupportedPayments?.SinglePayment,
+ Contains.Item(PaymentProduct.InstantSepaCreditTransfer));
+ Assert.That(institution.Result!.SupportedFeatures, Is.EqualTo(expectedSupportedFeatures));
+ Assert.That(institution.Result!.IdentificationCodes, Is.Empty);
+ });
+ }
+
+ #endregion
+
+ #region RequestsWithErrors
+
+ ///
+ /// Tests the retrieving of institutions for a country which is not covered by the API.
+ ///
+ [Test]
+ public async Task GetInstitutionsForNotCoveredCountry()
+ {
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.InstitutionsEndpointMockData.GetInstitutionsForNotCoveredCountry,
+ HttpStatusCode.BadRequest);
+
+ var response = await apiClient.InstitutionsEndpoint.GetInstitutions("US");
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ Assert.That(response.Error!.Detail, Is.EqualTo("US is not a valid choice."));
+ Assert.That(response.Error!.Summary, Is.EqualTo("Invalid country choice."));
+ });
+ }
+
+ ///
+ /// Tests the retrieving of an institution with an invalid id.
+ ///
+ [Test]
+ public async Task GetNonExistingInstitution()
+ {
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.InstitutionsEndpointMockData.GetNonExistingInstitution,
+ HttpStatusCode.NotFound);
+
+ var response = await apiClient.InstitutionsEndpoint.GetInstitution("invalid_id");
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.NotFound);
+ Assert.That(response.Error!.Detail, Is.EqualTo("Not found."));
+ Assert.That(response.Error!.Summary, Is.EqualTo("Not found."));
+ });
+ }
+
+ #endregion
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/RequisitionsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/RequisitionsEndpointTests.cs
new file mode 100644
index 0000000..1aecb45
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/RequisitionsEndpointTests.cs
@@ -0,0 +1,237 @@
+using FakeItEasy;
+using RobinTTY.NordigenApiClient.Models.Requests;
+using RobinTTY.NordigenApiClient.Models.Responses;
+using RobinTTY.NordigenApiClient.Tests.Shared;
+
+namespace RobinTTY.NordigenApiClient.Tests.Mocks.Endpoints;
+
+public class RequisitionsEndpointTests
+{
+ #region RequestsWithSuccessfulResponse
+
+ ///
+ /// Tests the retrieving of all existing requisitions.
+ ///
+ [Test]
+ public async Task GetRequisitions()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.RequisitionsEndpointMockData.GetRequisitions,
+ HttpStatusCode.OK);
+
+ var requisitions = await apiClient.RequisitionsEndpoint.GetRequisitions(100, 0);
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(requisitions, HttpStatusCode.OK);
+
+ var result = requisitions.Result!;
+ var requisition = result.Results.First();
+ var expectedAccountGuids = new List
+ {new("3fa85f64-5717-4562-b3fc-2c963f66afa6"), new("3fa85f64-5717-4562-b3fc-2c963f66afa7")};
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(result, Has.Count.EqualTo(1));
+ Assert.That(result.Next,
+ Is.EqualTo(new Uri("https://bankaccountdata.gocardless.com/api/v2/requisitions/?limit=100&offset=0")));
+ Assert.That(result.Previous,
+ Is.EqualTo(new Uri("https://bankaccountdata.gocardless.com/api/v2/requisitions/?limit=100&offset=0")));
+ Assert.That(requisition.Id, Is.EqualTo(new Guid("3fa85f64-5717-4562-b3fc-2c963f66afa6")));
+
+ Assert.That(requisition.Created.ToUniversalTime(),
+ Is.EqualTo(DateTime.Parse("2024-04-12T23:50:34.962Z").ToUniversalTime()));
+ Assert.That(requisition.Redirect, Is.EqualTo(new Uri("https://www.robintty.com")));
+ Assert.That(requisition.Status, Is.EqualTo(RequisitionStatus.Created));
+ Assert.That(requisition.InstitutionId, Is.EqualTo("SANDBOXFINANCE_SFIN0000"));
+
+ Assert.That(requisition.AgreementId, Is.EqualTo(new Guid("3fa85f64-5717-4562-b3fc-2c963f66afa6")));
+ Assert.That(requisition.Reference, Is.EqualTo("example-reference"));
+ Assert.That(requisition.Accounts, Is.EqualTo(expectedAccountGuids));
+ Assert.That(requisition.UserLanguage, Is.EqualTo("EN"));
+
+ Assert.That(requisition.AuthenticationLink,
+ Is.EqualTo(new Uri(
+ "https://ob.nordigen.com/psd2/start/3fa85f64-5717-4562-b3fc-2c963f66afa6/SANDBOXFINANCE_SFIN0000")));
+ Assert.That(requisition.SocialSecurityNumber, Is.EqualTo("555-50-1234"));
+ Assert.That(requisition.AccountSelection, Is.False);
+ Assert.That(requisition.RedirectImmediate, Is.True);
+ });
+ }
+
+ ///
+ /// Tests the retrieving of a specific requisition.
+ ///
+ [Test]
+ public async Task GetRequisition()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.RequisitionsEndpointMockData.GetRequisition,
+ HttpStatusCode.OK);
+
+ var requisition = await apiClient.RequisitionsEndpoint.GetRequisition(A.Dummy());
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(requisition, HttpStatusCode.OK);
+ var expectedAccountGuids = new List
+ {new("3fa85f64-5717-4562-b3fc-2c963f66afa6"), new("3fa85f64-5717-4562-b3fc-2c963f66afa7")};
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(requisition.Result!.Id, Is.EqualTo(new Guid("3fa85f64-5717-4562-b3fc-2c963f66afa6")));
+ Assert.That(requisition.Result!.Created.ToUniversalTime(),
+ Is.EqualTo(DateTime.Parse("2024-04-12T23:50:34.962Z").ToUniversalTime()));
+ Assert.That(requisition.Result!.Redirect, Is.EqualTo(new Uri("https://www.robintty.com")));
+ Assert.That(requisition.Result!.Status, Is.EqualTo(RequisitionStatus.Created));
+ Assert.That(requisition.Result!.InstitutionId, Is.EqualTo("SANDBOXFINANCE_SFIN0000"));
+
+ Assert.That(requisition.Result!.AgreementId, Is.EqualTo(new Guid("3fa85f64-5717-4562-b3fc-2c963f66afa6")));
+ Assert.That(requisition.Result!.Reference, Is.EqualTo("example-reference"));
+ Assert.That(requisition.Result!.Accounts, Is.EqualTo(expectedAccountGuids));
+ Assert.That(requisition.Result!.UserLanguage, Is.EqualTo("EN"));
+
+ Assert.That(requisition.Result!.AuthenticationLink,
+ Is.EqualTo(new Uri(
+ "https://ob.nordigen.com/psd2/start/3fa85f64-5717-4562-b3fc-2c963f66afa6/SANDBOXFINANCE_SFIN0000")));
+ Assert.That(requisition.Result!.SocialSecurityNumber, Is.EqualTo("555-50-1234"));
+ Assert.That(requisition.Result!.AccountSelection, Is.False);
+ Assert.That(requisition.Result!.RedirectImmediate, Is.True);
+ });
+ }
+
+ ///
+ /// Tests the creation of a new requisition.
+ ///
+ [Test]
+ public async Task CreateRequisitions()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.RequisitionsEndpointMockData.CreateRequisition,
+ HttpStatusCode.Created);
+
+ var requisition = await apiClient.RequisitionsEndpoint.CreateRequisition(A.Fake());
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(requisition, HttpStatusCode.Created);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(requisition.Result!.Redirect, Is.EqualTo(new Uri("https://www.robintty.com")));
+ Assert.That(requisition.Result!.InstitutionId, Is.EqualTo("SANDBOXFINANCE_SFIN0000"));
+ Assert.That(requisition.Result!.AgreementId, Is.EqualTo(new Guid("3fa85f64-5717-4562-b3fc-2c963f66afa6")));
+ Assert.That(requisition.Result!.Reference, Is.EqualTo("example-reference"));
+
+ Assert.That(requisition.Result!.UserLanguage, Is.EqualTo("EN"));
+ Assert.That(requisition.Result!.SocialSecurityNumber, Is.EqualTo("555-50-1234"));
+ Assert.That(requisition.Result!.AccountSelection, Is.False);
+ Assert.That(requisition.Result!.RedirectImmediate, Is.True);
+ });
+ }
+
+ ///
+ /// Tests the deletion of a requisition.
+ ///
+ [Test]
+ public async Task DeleteRequisitions()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.RequisitionsEndpointMockData.DeleteRequisition,
+ HttpStatusCode.OK);
+
+ var result = await apiClient.RequisitionsEndpoint.DeleteRequisition(A.Dummy());
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(result, HttpStatusCode.OK);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(result.Result?.Summary, Is.EqualTo("Requisition deleted"));
+ Assert.That(result.Result?.Detail,
+ Is.EqualTo(
+ "Requisition b5462cad-5a7f-42e1-881d-d0fa066f54bc deleted with all its End User Agreements"));
+ });
+ }
+
+ #endregion
+
+ #region RequestsWithErrors
+
+ ///
+ /// Tests the retrieval of a requisition with an invalid guid.
+ ///
+ [Test]
+ public async Task GetRequisitionWithInvalidGuid()
+ {
+ const string guid = "f84d7b8-dee4-4cd9-bc6d-842ef78f6028";
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.RequisitionsEndpointMockData.GetRequisitionWithInvalidGuid, HttpStatusCode.NotFound);
+
+ var response = await apiClient.RequisitionsEndpoint.GetRequisition(guid);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.NotFound);
+ Assert.That(response.Error!.Detail, Is.EqualTo("Not found."));
+ });
+ }
+
+ ///
+ /// Tests the creation of an end user agreement with invalid id.
+ ///
+ [Test]
+ public async Task CreateRequisitionWithInvalidId()
+ {
+ var redirect = new Uri("ftp://ftp.test.com");
+ var agreementId = Guid.Empty;
+ var requisitionRequest =
+ new CreateRequisitionRequest(redirect, "123", "internal_reference", "EN", agreementId, null, true, true);
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.RequisitionsEndpointMockData.CreateRequisitionWithInvalidId, HttpStatusCode.BadRequest);
+
+ var response = await apiClient.RequisitionsEndpoint.CreateRequisition(requisitionRequest);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+ Assert.That(response.Error!.Summary, Is.EqualTo("Invalid ID"));
+ Assert.That(response.Error!.Detail,
+ Is.EqualTo("00000000-0000-0000-0000-000000000000 is not a valid UUID. "));
+ });
+ }
+
+ ///
+ /// Tests the creation of an end user agreement with invalid parameters in the .
+ ///
+ [Test]
+ public async Task CreateRequisitionWithInvalidParameters()
+ {
+ var redirect = new Uri("ftp://ftp.test.com");
+ // Agreement belongs to SANDBOXFINANCE_SFIN0000
+ var agreementId = Guid.Parse("f34c3c71-4a62-4a25-b998-3f37ddce84a2");
+ var requisitionRequest =
+ new CreateRequisitionRequest(redirect, "", "", "AB", agreementId, "12345", true, true);
+ var apiClient = TestHelpers.GetMockClient(
+ TestHelpers.MockData.RequisitionsEndpointMockData.CreateRequisitionWithInvalidParameters, HttpStatusCode.BadRequest);
+
+ var response = await apiClient.RequisitionsEndpoint.CreateRequisition(requisitionRequest);
+
+ Assert.Multiple(() =>
+ {
+ AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);
+
+ Assert.That(response.Error!.AccountSelectionError!.Summary, Is.EqualTo("Account selection not supported"));
+ Assert.That(response.Error!.AccountSelectionError!.Detail,
+ Is.EqualTo("Account selection not supported for "));
+
+ Assert.That(response.Error!.AgreementError!.Summary, Is.EqualTo("Incorrect Institution ID "));
+ Assert.That(response.Error!.AgreementError!.Detail,
+ Is.EqualTo(
+ "Provided Institution ID: '' for requisition does not match EUA institution ID 'SANDBOXFINANCE_SFIN0000'. Please provide correct institution ID: 'SANDBOXFINANCE_SFIN0000'"));
+
+ Assert.That(response.Error!.InstitutionIdError!.Summary, Is.EqualTo("This field may not be blank."));
+ Assert.That(response.Error!.InstitutionIdError!.Detail, Is.EqualTo("This field may not be blank."));
+
+ Assert.That(response.Error!.InstitutionIdError!.Summary, Is.EqualTo("This field may not be blank."));
+ Assert.That(response.Error!.InstitutionIdError!.Detail, Is.EqualTo("This field may not be blank."));
+
+ Assert.That(response.Error!.SocialSecurityNumberError!.Summary,
+ Is.EqualTo("SSN verification not supported"));
+ Assert.That(response.Error!.SocialSecurityNumberError!.Detail,
+ Is.EqualTo("SSN verification not supported for "));
+
+ Assert.That(response.Error!.UserLanguageError!.Summary,
+ Is.EqualTo("Provided user_language is invalid or not supported"));
+ Assert.That(response.Error!.UserLanguageError!.Detail,
+ Is.EqualTo("'AB' is an invalid or unsupported language"));
+ });
+ }
+
+ #endregion
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/TokenEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/TokenEndpointTests.cs
new file mode 100644
index 0000000..16e0c5f
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/TokenEndpointTests.cs
@@ -0,0 +1,69 @@
+using FakeItEasy;
+using Microsoft.IdentityModel.JsonWebTokens;
+using RobinTTY.NordigenApiClient.Tests.Shared;
+using RobinTTY.NordigenApiClient.Utility;
+
+namespace RobinTTY.NordigenApiClient.Tests.Mocks.Endpoints;
+
+public class TokenEndpointTests
+{
+ #region RequestsWithSuccessfulResponse
+
+ ///
+ /// Tests the retrieving of a new token.
+ ///
+ [Test]
+ public async Task GetNewToken()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.TokenEndpointMockData.GetNewToken,
+ HttpStatusCode.OK, addDefaultAuthToken: false);
+
+ var tokenPair = await apiClient.TokenEndpoint.GetTokenPair();
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(tokenPair, HttpStatusCode.OK);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(tokenPair.Result!.AccessExpires, Is.EqualTo(86_400));
+ Assert.That(tokenPair.Result!.AccessToken.IsExpired(), Is.False);
+ Assert.That(tokenPair.Result!.AccessToken.EncodedToken,
+ Is.EqualTo(
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwic3ViIjoiMTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjozMzI3MDExNzU5NH0.gEa5VdPSqZW2xk9IqCEqiw6bzBOer_uAR1yp2XK7FFo"));
+
+ Assert.That(tokenPair.Result!.RefreshExpires, Is.EqualTo(2_592_000));
+ Assert.That(tokenPair.Result!.RefreshToken.IsExpired(), Is.False);
+ Assert.That(tokenPair.Result!.RefreshToken.EncodedToken,
+ Is.EqualTo(
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MzMyNzAxMTc1OTR9.xfOrczY3KvG-SiHLZkVLPas017ZX8DHkcCN78Xd9cac"));
+ });
+ }
+
+ ///
+ /// Tests the retrieving of a new token.
+ ///
+ [Test]
+ public async Task RefreshAccessToken()
+ {
+ var apiClient = TestHelpers.GetMockClient(TestHelpers.MockData.TokenEndpointMockData.RefreshAccessToken,
+ HttpStatusCode.OK, addDefaultAuthToken: false);
+
+ var tokenPair = await apiClient.TokenEndpoint.RefreshAccessToken(A.Fake(options =>
+ {
+ options.WithArgumentsForConstructor(new object[]
+ {
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MzMyNzAxMTc1OTR9.xfOrczY3KvG-SiHLZkVLPas017ZX8DHkcCN78Xd9cac"
+ });
+ }));
+ AssertionHelpers.AssertNordigenApiResponseIsSuccessful(tokenPair, HttpStatusCode.OK);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(tokenPair.Result!.AccessExpires, Is.EqualTo(86_400));
+ Assert.That(tokenPair.Result!.AccessToken.IsExpired(), Is.False);
+ Assert.That(tokenPair.Result!.AccessToken.EncodedToken,
+ Is.EqualTo(
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwic3ViIjoiMTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjozMzI3MDExNzU5NH0.gEa5VdPSqZW2xk9IqCEqiw6bzBOer_uAR1yp2XK7FFo"));
+ });
+ }
+
+ #endregion
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Mocks/FakeHttpMessageHandler.cs b/src/RobinTTY.NordigenApiClient.Tests/Mocks/FakeHttpMessageHandler.cs
new file mode 100644
index 0000000..b184a01
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/Mocks/FakeHttpMessageHandler.cs
@@ -0,0 +1,12 @@
+namespace RobinTTY.NordigenApiClient.Tests.Mocks;
+
+public abstract class FakeHttpMessageHandler : HttpMessageHandler
+{
+ public abstract Task FakeSendAsync(
+ HttpRequestMessage request, CancellationToken cancellationToken);
+
+ // sealed so FakeItEasy won't intercept calls to this method
+ protected sealed override Task SendAsync(
+ HttpRequestMessage request, CancellationToken cancellationToken)
+ => FakeSendAsync(request, cancellationToken);
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Mocks/Responses/MockResponsesModel.cs b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Responses/MockResponsesModel.cs
new file mode 100644
index 0000000..8ec5a06
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Responses/MockResponsesModel.cs
@@ -0,0 +1,122 @@
+using RobinTTY.NordigenApiClient.Models.Errors;
+using RobinTTY.NordigenApiClient.Models.Jwt;
+using RobinTTY.NordigenApiClient.Models.Responses;
+
+namespace RobinTTY.NordigenApiClient.Tests.Mocks.Responses;
+
+internal class MockResponsesModel(
+ AccountsEndpointMockData accountsEndpointMockData,
+ AgreementsEndpointMockData agreementsEndpointMockData,
+ InstitutionsEndpointMockData institutionsEndpointMockData,
+ RequisitionsEndpointMockData requisitionsEndpointMockData,
+ TokenEndpointMockData tokenEndpointMockData,
+ CredentialMockData credentialMockData)
+{
+ public AccountsEndpointMockData AccountsEndpointMockData { get; set; } = accountsEndpointMockData;
+ public AgreementsEndpointMockData AgreementsEndpointMockData { get; set; } = agreementsEndpointMockData;
+ public InstitutionsEndpointMockData InstitutionsEndpointMockData { get; set; } = institutionsEndpointMockData;
+ public RequisitionsEndpointMockData RequisitionsEndpointMockData { get; set; } = requisitionsEndpointMockData;
+ public TokenEndpointMockData TokenEndpointMockData { get; set; } = tokenEndpointMockData;
+ public CredentialMockData CredentialMockData { get; set; } = credentialMockData;
+}
+
+internal class AccountsEndpointMockData(
+ BankAccount getAccount,
+ BalanceJsonWrapper getBalances,
+ BankAccountDetailsWrapper getAccountDetails,
+ AccountTransactionsWrapper getTransactions,
+ AccountTransactionsWrapper getTransactionRange,
+ AccountsError getTransactionRangeInFuture,
+ AccountsError getAccountWithInvalidGuid,
+ AccountsError getAccountThatDoesNotExist,
+ AccountsError getBalancesForAccountThatDoesNotExist)
+{
+ public BankAccount GetAccount { get; set; } = getAccount;
+ public BalanceJsonWrapper GetBalances { get; set; } = getBalances;
+ public BankAccountDetailsWrapper GetAccountDetails { get; set; } = getAccountDetails;
+ public AccountTransactionsWrapper GetTransactions { get; set; } = getTransactions;
+ public AccountTransactionsWrapper GetTransactionRange { get; set; } = getTransactionRange;
+ public AccountsError GetTransactionRangeInFuture { get; set; } = getTransactionRangeInFuture;
+ public AccountsError GetAccountWithInvalidGuid { get; set; } = getAccountWithInvalidGuid;
+ public AccountsError GetAccountThatDoesNotExist { get; set; } = getAccountThatDoesNotExist;
+ public AccountsError GetBalancesForAccountThatDoesNotExist { get; set; } = getBalancesForAccountThatDoesNotExist;
+}
+
+internal class AgreementsEndpointMockData(
+ ResponsePage getAgreements,
+ Agreement createAgreement,
+ Agreement getAgreement,
+ BasicResponse deleteAgreement,
+ BasicResponse getAgreementWithInvalidGuid,
+ CreateAgreementError createAgreementWithInvalidInstitutionId,
+ CreateAgreementError createAgreementWithInvalidParams,
+ CreateAgreementError createAgreementWithEmptyInstitutionIdAndAccessScopes,
+ CreateAgreementError createAgreementWithInvalidParamsAtPolishInstitution)
+{
+ public ResponsePage GetAgreements { get; set; } = getAgreements;
+ public Agreement CreateAgreement { get; set; } = createAgreement;
+ public Agreement GetAgreement { get; set; } = getAgreement;
+ public BasicResponse DeleteAgreement { get; set; } = deleteAgreement;
+
+ public BasicResponse GetAgreementWithInvalidGuid { get; set; } = getAgreementWithInvalidGuid;
+
+ public CreateAgreementError CreateAgreementWithInvalidInstitutionId { get; set; } =
+ createAgreementWithInvalidInstitutionId;
+
+ public CreateAgreementError CreateAgreementWithInvalidParams { get; set; } = createAgreementWithInvalidParams;
+
+ public CreateAgreementError CreateAgreementWithEmptyInstitutionIdAndAccessScopes { get; set; } =
+ createAgreementWithEmptyInstitutionIdAndAccessScopes;
+
+ public CreateAgreementError CreateAgreementWithInvalidParamsAtPolishInstitution { get; set; } =
+ createAgreementWithInvalidParamsAtPolishInstitution;
+}
+
+internal class InstitutionsEndpointMockData(
+ List getInstitutions,
+ Institution getInstitution,
+ InstitutionsErrorInternal getInstitutionsForNotCoveredCountry,
+ BasicResponse getNonExistingInstitution)
+{
+ public List GetInstitutions { get; set; } = getInstitutions;
+ public Institution GetInstitution { get; set; } = getInstitution;
+
+ public InstitutionsErrorInternal GetInstitutionsForNotCoveredCountry { get; set; } =
+ getInstitutionsForNotCoveredCountry;
+
+ public BasicResponse GetNonExistingInstitution { get; set; } = getNonExistingInstitution;
+}
+
+internal class RequisitionsEndpointMockData(
+ ResponsePage getRequisitions,
+ Requisition getRequisition,
+ Requisition createRequisition,
+ BasicResponse deleteRequisition,
+ BasicResponse getRequisitionWithInvalidGuid,
+ CreateRequisitionError createRequisitionWithInvalidId,
+ CreateRequisitionError createRequisitionWithInvalidParameters)
+{
+ public ResponsePage GetRequisitions { get; set; } = getRequisitions;
+ public Requisition GetRequisition { get; set; } = getRequisition;
+ public Requisition CreateRequisition { get; set; } = createRequisition;
+ public BasicResponse DeleteRequisition { get; set; } = deleteRequisition;
+ public BasicResponse GetRequisitionWithInvalidGuid { get; set; } = getRequisitionWithInvalidGuid;
+ public CreateRequisitionError CreateRequisitionWithInvalidId { get; set; } = createRequisitionWithInvalidId;
+
+ public CreateRequisitionError CreateRequisitionWithInvalidParameters { get; set; } =
+ createRequisitionWithInvalidParameters;
+}
+
+internal class TokenEndpointMockData(
+ JsonWebTokenPair getNewToken,
+ JsonWebAccessToken refreshAccessToken)
+{
+ public JsonWebTokenPair GetNewToken { get; set; } = getNewToken;
+ public JsonWebAccessToken RefreshAccessToken { get; set; } = refreshAccessToken;
+}
+
+internal class CredentialMockData(BasicResponse noAccountForGivenCredentialsError, BasicResponse ipNotWhitelistedError)
+{
+ public BasicResponse NoAccountForGivenCredentialsError { get; set; } = noAccountForGivenCredentialsError;
+ public BasicResponse IpNotWhitelistedError { get; set; } = ipNotWhitelistedError;
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Mocks/Responses/responses.json b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Responses/responses.json
new file mode 100644
index 0000000..304dc87
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Responses/responses.json
@@ -0,0 +1,519 @@
+{
+ "AccountsEndpointMockData": {
+ "GetAccount": {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "created": "2024-04-05T21:51:12.694Z",
+ "last_accessed": "2024-04-05T21:51:12.694Z",
+ "iban": "GL2010440000010445",
+ "institution_id": "SANDBOXFINANCE_SFIN0000",
+ "status": "READY",
+ "owner_name": "John Doe"
+ },
+ "GetBalances": {
+ "balances": [
+ {
+ "balanceAmount": {
+ "amount": "1913.12",
+ "currency": "EUR"
+ },
+ "balanceType": "closingAvailable",
+ "referenceDate": "2021-11-22"
+ },
+ {
+ "balanceAmount": {
+ "amount": "1913.12",
+ "currency": "EUR"
+ },
+ "balanceType": "forwardAvailable",
+ "referenceDate": "2021-11-19"
+ }
+ ]
+ },
+ "GetAccountDetails": {
+ "account": {
+ "resourceId": "abc",
+ "iban": "GL2010440000010445",
+ "currency": "EUR",
+ "ownerName": "Jane Doe",
+ "name": "Main Account",
+ "product": "Credit Card",
+ "cashAccountType": "CACC"
+ }
+ },
+ "GetTransactions": {
+ "transactions": {
+ "booked": [
+ {
+ "transactionId": "string",
+ "debtorName": "MON MOTHMA",
+ "debtorAccount": {
+ "iban": "GL2010440000010445"
+ },
+ "transactionAmount": {
+ "currency": "EUR",
+ "amount": "45.00"
+ },
+ "bankTransactionCode": "PMNT",
+ "bookingDate": "2021-11-19",
+ "valueDate": "2021-11-19",
+ "remittanceInformationUnstructured": "For the support of Restoration of the Republic foundation"
+ },
+ {
+ "transactionId": "string",
+ "transactionAmount": {
+ "currency": "string",
+ "amount": "947.26"
+ },
+ "bankTransactionCode": "string",
+ "bookingDate": "2021-11-19",
+ "valueDate": "2021-11-19",
+ "remittanceInformationUnstructured": "string"
+ }
+ ],
+ "pending": [
+ {
+ "transactionAmount": {
+ "currency": "string",
+ "amount": "99.20"
+ },
+ "valueDate": "2021-11-19",
+ "remittanceInformationUnstructured": "string"
+ }
+ ]
+ }
+ },
+ "GetTransactionRange": {
+ "transactions": {
+ "booked": [
+ {
+ "transactionId": "string",
+ "debtorName": "MON MOTHMA",
+ "debtorAccount": {
+ "iban": "GL2010440000010445"
+ },
+ "transactionAmount": {
+ "currency": "EUR",
+ "amount": "45.00"
+ },
+ "bankTransactionCode": "PMNT",
+ "bookingDate": "2021-11-19",
+ "valueDate": "2021-11-19",
+ "remittanceInformationUnstructured": "For the support of Restoration of the Republic foundation"
+ },
+ {
+ "transactionId": "string",
+ "transactionAmount": {
+ "currency": "string",
+ "amount": "947.26"
+ },
+ "bankTransactionCode": "string",
+ "bookingDate": "2021-11-19",
+ "valueDate": "2021-11-19",
+ "remittanceInformationUnstructured": "string"
+ }
+ ],
+ "pending": [
+ {
+ "transactionAmount": {
+ "currency": "string",
+ "amount": "99.20"
+ },
+ "valueDate": "2021-11-19",
+ "remittanceInformationUnstructured": "string"
+ }
+ ]
+ }
+ },
+ "GetAccountWithInvalidGuid": {
+ "summary": "Invalid Account ID",
+ "detail": "abcdefg is not a valid Account UUID. ",
+ "status_code": 400
+ },
+ "GetAccountThatDoesNotExist": {
+ "detail": "Not found.",
+ "summary": "Not found.",
+ "status_code": 404
+ },
+ "GetBalancesForAccountThatDoesNotExist": {
+ "summary": "Account ID f1d53c46-260d-4556-82df-4e5fed58e37c not found",
+ "detail": "Please check whether you specified a valid Account ID",
+ "status_code": 404
+ },
+ "GetTransactionRangeInFuture": {
+ "date_from": {
+ "summary": "Date can't be in future",
+ "detail": "'2024-04-21' can't be greater than 2024-04-20. Specify correct date range"
+ },
+ "date_to": {
+ "summary": "Date can't be in future",
+ "detail": "'2024-05-21' can't be greater than 2024-04-20. Specify correct date range"
+ },
+ "status_code": 400
+ }
+ },
+ "AgreementsEndpointMockData": {
+ "GetAgreements": {
+ "count": 1,
+ "next": "https://bankaccountdata.gocardless.com/api/v2/agreements/enduser/?limit=100&offset=0",
+ "previous": "https://bankaccountdata.gocardless.com/api/v2/agreements/enduser/?limit=100&offset=0",
+ "results": [
+ {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "created": "2024-04-08T20:57:00.550Z",
+ "institution_id": "SANDBOXFINANCE_SFIN0000",
+ "max_historical_days": 90,
+ "access_valid_for_days": 90,
+ "access_scope": [
+ "balances",
+ "details",
+ "transactions"
+ ],
+ "accepted": "2024-04-08T20:57:00.550Z"
+ }
+ ]
+ },
+ "GetAgreement": {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "created": "2024-04-08T22:54:54.869Z",
+ "institution_id": "SANDBOXFINANCE_SFIN0000",
+ "max_historical_days": 90,
+ "access_valid_for_days": 90,
+ "access_scope": [
+ "balances",
+ "details",
+ "transactions"
+ ],
+ "accepted": "2024-04-08T22:54:54.869Z"
+ },
+ "CreateAgreement": {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa7",
+ "institution_id": "SANDBOXFINANCE_SFIN0000",
+ "max_historical_days": 145,
+ "access_valid_for_days": 145,
+ "access_scope": [
+ "balances",
+ "details",
+ "transactions"
+ ]
+ },
+ "DeleteAgreement": {
+ "summary": "End User Agreement deleted",
+ "detail": "End User Agreement bb37bc52-5b1d-44f9-b1cd-ec9594f25387 deleted"
+ },
+ "GetAgreementWithInvalidGuid": {
+ "summary": "Invalid EndUserAgreement ID",
+ "detail": "f84d7b8-dee4-4cd9-bc6d-842ef78f6028 is not a valid EndUserAgreement UUID. ",
+ "status_code": 400
+ },
+ "CreateAgreementWithInvalidInstitutionId": {
+ "institution_id": {
+ "summary": "Unknown Institution ID invalid_institution",
+ "detail": "Get Institution IDs from /institutions/?country\u003d{$COUNTRY_CODE}"
+ },
+ "status_code": 400
+ },
+ "CreateAgreementWithInvalidParams": {
+ "max_historical_days": {
+ "summary": "Incorrect max_historical_days",
+ "detail": "max_historical_days must be \u003e 0 and \u003c\u003d SANDBOXFINANCE_SFIN0000 transaction_total_days (90)"
+ },
+ "access_valid_for_days": {
+ "summary": "Incorrect access_valid_for_days",
+ "detail": "access_valid_for_days must be \u003e 0 and \u003c\u003d 180"
+ },
+ "access_scope": {
+ "summary": "Unknown value \u0027[\u0027invalid2\u0027, \u0027invalid\u0027]\u0027 in access_scope",
+ "detail": "Choose one or several from [\u0027balances\u0027, \u0027details\u0027, \u0027transactions\u0027]"
+ },
+ "status_code": 400
+ },
+ "CreateAgreementWithEmptyInstitutionIdAndAccessScopes": {
+ "institution_id": [
+ "This field may not be null."
+ ],
+ "access_scope": [
+ "This field may not be null."
+ ],
+ "status_code": 400
+ },
+ "CreateAgreementWithInvalidParamsAtPolishInstitution": {
+ "summary": [
+ "Institution access scope dependencies error"
+ ],
+ "detail": [
+ "For this institution the following scopes are required together: [\u0027details\u0027, \u0027balances\u0027]"
+ ],
+ "status_code": 400
+ }
+ },
+ "InstitutionsEndpointMockData": {
+ "GetInstitutions": [
+ {
+ "id": "N26_NTSBDEB1",
+ "name": "N26 Bank",
+ "bic": "NTSBDEB1",
+ "transaction_total_days": "90",
+ "countries": [
+ "GB",
+ "NO",
+ "SE",
+ "FI",
+ "DK",
+ "EE",
+ "LV",
+ "LT",
+ "NL",
+ "CZ",
+ "ES",
+ "PL",
+ "BE",
+ "DE",
+ "AT",
+ "BG",
+ "HR",
+ "CY",
+ "FR",
+ "GR",
+ "HU",
+ "IS",
+ "IE",
+ "IT",
+ "LI",
+ "LU",
+ "MT",
+ "PT",
+ "RO",
+ "SK",
+ "SI"
+ ],
+ "logo": "https://cdn-logos.gocardless.com/ais/N26_SANDBOX_NTSBDEB1.png",
+ "identification_codes": []
+ },
+ {
+ "id": "N26_NTSBDEB1",
+ "name": "N26 Bank",
+ "bic": "NTSBDEB1",
+ "transaction_total_days": "90",
+ "countries": [
+ "GB",
+ "NO",
+ "SE",
+ "FI",
+ "DK",
+ "EE",
+ "LV",
+ "LT",
+ "NL",
+ "CZ",
+ "ES",
+ "PL",
+ "BE",
+ "DE",
+ "AT",
+ "BG",
+ "HR",
+ "CY",
+ "FR",
+ "GR",
+ "HU",
+ "IS",
+ "IE",
+ "IT",
+ "LI",
+ "LU",
+ "MT",
+ "PT",
+ "RO",
+ "SK",
+ "SI"
+ ],
+ "logo": "https://cdn-logos.gocardless.com/ais/N26_SANDBOX_NTSBDEB1.png",
+ "identification_codes": []
+ }
+ ],
+ "GetInstitution": {
+ "id": "N26_NTSBDEB1",
+ "name": "N26 Bank",
+ "bic": "NTSBDEB1",
+ "transaction_total_days": "90",
+ "countries": [
+ "GB",
+ "NO",
+ "SE",
+ "FI",
+ "DK",
+ "EE",
+ "LV",
+ "LT",
+ "NL",
+ "CZ",
+ "ES",
+ "PL",
+ "BE",
+ "DE",
+ "AT",
+ "BG",
+ "HR",
+ "CY",
+ "FR",
+ "GR",
+ "HU",
+ "IS",
+ "IE",
+ "IT",
+ "LI",
+ "LU",
+ "MT",
+ "PT",
+ "RO",
+ "SK",
+ "SI"
+ ],
+ "logo": "https://cdn-logos.gocardless.com/ais/N26_SANDBOX_NTSBDEB1.png",
+ "supported_payments": {
+ "single-payment": [
+ "SCT",
+ "ISCT"
+ ]
+ },
+ "supported_features": [
+ "account_selection",
+ "business_accounts",
+ "card_accounts",
+ "payments",
+ "private_accounts"
+ ],
+ "identification_codes": []
+ },
+ "GetInstitutionsForNotCoveredCountry": {
+ "country": {
+ "summary": "Invalid country choice.",
+ "detail": "US is not a valid choice."
+ },
+ "status_code": 400
+ },
+ "GetNonExistingInstitution": {
+ "summary": "Not found.",
+ "detail": "Not found.",
+ "status_code": 404
+ }
+ },
+ "RequisitionsEndpointMockData": {
+ "GetRequisitions": {
+ "count": 1,
+ "next": "https://bankaccountdata.gocardless.com/api/v2/requisitions/?limit=100&offset=0",
+ "previous": "https://bankaccountdata.gocardless.com/api/v2/requisitions/?limit=100&offset=0",
+ "results": [
+ {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "created": "2024-04-12T23:50:34.962Z",
+ "redirect": "https://www.robintty.com",
+ "status": "CR",
+ "institution_id": "SANDBOXFINANCE_SFIN0000",
+ "agreement": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "reference": "example-reference",
+ "accounts": [
+ "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "3fa85f64-5717-4562-b3fc-2c963f66afa7"
+ ],
+ "user_language": "EN",
+ "link": "https://ob.nordigen.com/psd2/start/3fa85f64-5717-4562-b3fc-2c963f66afa6/SANDBOXFINANCE_SFIN0000",
+ "ssn": "555-50-1234",
+ "account_selection": false,
+ "redirect_immediate": true
+ }
+ ]
+ },
+ "GetRequisition": {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "created": "2024-04-12T23:50:34.962Z",
+ "redirect": "https://www.robintty.com",
+ "status": "CR",
+ "institution_id": "SANDBOXFINANCE_SFIN0000",
+ "agreement": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "reference": "example-reference",
+ "accounts": [
+ "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "3fa85f64-5717-4562-b3fc-2c963f66afa7"
+ ],
+ "user_language": "EN",
+ "link": "https://ob.nordigen.com/psd2/start/3fa85f64-5717-4562-b3fc-2c963f66afa6/SANDBOXFINANCE_SFIN0000",
+ "ssn": "555-50-1234",
+ "account_selection": false,
+ "redirect_immediate": true
+ },
+ "CreateRequisition": {
+ "redirect": "https://www.robintty.com",
+ "institution_id": "SANDBOXFINANCE_SFIN0000",
+ "agreement": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "reference": "example-reference",
+ "user_language": "EN",
+ "ssn": "555-50-1234",
+ "account_selection": false,
+ "redirect_immediate": true
+ },
+ "DeleteRequisition": {
+ "summary": "Requisition deleted",
+ "detail": "Requisition b5462cad-5a7f-42e1-881d-d0fa066f54bc deleted with all its End User Agreements"
+ },
+ "GetRequisitionWithInvalidGuid": {
+ "summary": "Not found.",
+ "detail": "Not found.",
+ "status_code": 404
+ },
+ "CreateRequisitionWithInvalidId": {
+ "summary": "Invalid ID",
+ "detail": "00000000-0000-0000-0000-000000000000 is not a valid UUID. ",
+ "status_code": 400
+ },
+ "CreateRequisitionWithInvalidParameters": {
+ "institution_id": [
+ "This field may not be blank."
+ ],
+ "agreement": {
+ "summary": "Incorrect Institution ID ",
+ "detail": "Provided Institution ID: \u0027\u0027 for requisition does not match EUA institution ID \u0027SANDBOXFINANCE_SFIN0000\u0027. Please provide correct institution ID: \u0027SANDBOXFINANCE_SFIN0000\u0027"
+ },
+ "reference": [
+ "This field may not be blank."
+ ],
+ "user_language": {
+ "summary": "Provided user_language is invalid or not supported",
+ "detail": "\u0027AB\u0027 is an invalid or unsupported language"
+ },
+ "ssn": {
+ "summary": "SSN verification not supported",
+ "detail": "SSN verification not supported for "
+ },
+ "account_selection": {
+ "summary": "Account selection not supported",
+ "detail": "Account selection not supported for "
+ },
+ "status_code": 400
+ }
+ },
+ "TokenEndpointMockData": {
+ "GetNewToken": {
+ "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwic3ViIjoiMTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjozMzI3MDExNzU5NH0.gEa5VdPSqZW2xk9IqCEqiw6bzBOer_uAR1yp2XK7FFo",
+ "access_expires": 86400,
+ "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MzMyNzAxMTc1OTR9.xfOrczY3KvG-SiHLZkVLPas017ZX8DHkcCN78Xd9cac",
+ "refresh_expires": 2592000
+ },
+ "RefreshAccessToken": {
+ "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwic3ViIjoiMTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjozMzI3MDExNzU5NH0.gEa5VdPSqZW2xk9IqCEqiw6bzBOer_uAR1yp2XK7FFo",
+ "access_expires": 86400
+ }
+ },
+ "CredentialMockData": {
+ "NoAccountForGivenCredentialsError": {
+ "summary": "Authentication failed",
+ "detail": "No active account found with the given credentials",
+ "status_code": 401
+ },
+ "IpNotWhitelistedError": {
+ "summary": "IP address access denied",
+ "detail": "Your IP 127.0.0.1 isn't whitelisted to perform this action",
+ "status_code": 403
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RobinTTY.NordigenApiClient.Tests/NordigenApiClientTests.cs b/src/RobinTTY.NordigenApiClient.Tests/NordigenApiClientTests.cs
deleted file mode 100644
index 1bf94e1..0000000
--- a/src/RobinTTY.NordigenApiClient.Tests/NordigenApiClientTests.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System.Net;
-
-namespace RobinTTY.NordigenApiClient.Tests;
-
-public class NordigenApiClientTests
-{
- ///
- /// Executes a request to the Nordigen API using the default base address.
- ///
- [Test]
- public async Task ExecuteRequestWithDefaultBaseAddress()
- {
- var apiClient = TestExtensions.GetConfiguredClient();
- await ExecuteExampleRequest(apiClient);
- }
-
- ///
- /// Executes a request to the Nordigen API using a custom base address.
- ///
- [Test]
- public async Task ExecuteRequestWithCustomBaseAddress()
- {
- var apiClient = TestExtensions.GetConfiguredClient("https://ob.gocardless.com/api/v2/");
- await ExecuteExampleRequest(apiClient);
- }
-
- private async Task ExecuteExampleRequest(NordigenClient apiClient)
- {
- var response = await apiClient.TokenEndpoint.GetTokenPair();
- TestExtensions.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.OK);
- var response2 = await apiClient.TokenEndpoint.RefreshAccessToken(response.Result!.RefreshToken);
- TestExtensions.AssertNordigenApiResponseIsSuccessful(response2, HttpStatusCode.OK);
- }
-}
\ No newline at end of file
diff --git a/src/RobinTTY.NordigenApiClient.Tests/RobinTTY.NordigenApiClient.Tests.csproj b/src/RobinTTY.NordigenApiClient.Tests/RobinTTY.NordigenApiClient.Tests.csproj
index e48fae7..13a0390 100644
--- a/src/RobinTTY.NordigenApiClient.Tests/RobinTTY.NordigenApiClient.Tests.csproj
+++ b/src/RobinTTY.NordigenApiClient.Tests/RobinTTY.NordigenApiClient.Tests.csproj
@@ -9,10 +9,11 @@
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -30,6 +31,9 @@
PreserveNewest
+
+ Always
+
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Serialization/CreateAgreementErrorTests.cs b/src/RobinTTY.NordigenApiClient.Tests/Serialization/CreateAgreementErrorTests.cs
new file mode 100644
index 0000000..be78afb
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/Serialization/CreateAgreementErrorTests.cs
@@ -0,0 +1,31 @@
+using System.Text.Json;
+using RobinTTY.NordigenApiClient.JsonConverters;
+using RobinTTY.NordigenApiClient.Models.Errors;
+
+namespace RobinTTY.NordigenApiClient.Tests.Serialization;
+
+public class CreateAgreementErrorTests
+{
+ ///
+ /// Tests the correct deserialization of .
+ ///
+ [Test]
+ public void DeserializeTransactionWithSingleCurrencyExchange()
+ {
+ const string json = "{\n \"summary\": [\n \"Institution access scope dependencies error\",\n \"Some Other Error Summary\"\n ],\n \"detail\": [\n \"For this institution the following scopes are required together: [\\u0027details\\u0027, \\u0027balances\\u0027]\",\n \"Some Other Error Detail\"\n ],\n \"status_code\": 400\n}\n";
+
+ var options = new JsonSerializerOptions
+ {
+ Converters = {new StringArrayMergeConverter()}
+ };
+ var createAgreementError = JsonSerializer.Deserialize(json, options);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(createAgreementError!.Summary, Is.Not.Null);
+ Assert.That(createAgreementError.Detail, Is.Not.Null);
+ Assert.That(createAgreementError.Summary, Is.EqualTo("Institution access scope dependencies error; Some Other Error Summary"));
+ Assert.That(createAgreementError.Detail, Is.EqualTo("For this institution the following scopes are required together: ['details', 'balances']; Some Other Error Detail"));
+ });
+ }
+}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Serialization/TransactionTests.cs b/src/RobinTTY.NordigenApiClient.Tests/Serialization/TransactionTests.cs
index 431eba6..5cf2a0c 100644
--- a/src/RobinTTY.NordigenApiClient.Tests/Serialization/TransactionTests.cs
+++ b/src/RobinTTY.NordigenApiClient.Tests/Serialization/TransactionTests.cs
@@ -1,5 +1,4 @@
-using System.Net;
-using System.Runtime.Serialization;
+using System.Runtime.Serialization;
using System.Text.Json;
using RobinTTY.NordigenApiClient.JsonConverters;
using RobinTTY.NordigenApiClient.Models.Errors;
@@ -66,7 +65,6 @@ public void DeserializeTransactionWithMultipleCurrencyExchange()
///
/// Tests that a malformed json throws a human readable error containing the raw json content of the API response.
///
- ///
[Test]
public async Task DeserializeWithException()
{
diff --git a/src/RobinTTY.NordigenApiClient.Tests/TestExtensions.cs b/src/RobinTTY.NordigenApiClient.Tests/Shared/AssertionHelpers.cs
similarity index 50%
rename from src/RobinTTY.NordigenApiClient.Tests/TestExtensions.cs
rename to src/RobinTTY.NordigenApiClient.Tests/Shared/AssertionHelpers.cs
index 91de31e..0f33bc2 100644
--- a/src/RobinTTY.NordigenApiClient.Tests/TestExtensions.cs
+++ b/src/RobinTTY.NordigenApiClient.Tests/Shared/AssertionHelpers.cs
@@ -1,11 +1,8 @@
-using System.Net;
-using RobinTTY.NordigenApiClient.Endpoints;
-using RobinTTY.NordigenApiClient.Models;
-using RobinTTY.NordigenApiClient.Models.Responses;
+using RobinTTY.NordigenApiClient.Models.Responses;
-namespace RobinTTY.NordigenApiClient.Tests;
+namespace RobinTTY.NordigenApiClient.Tests.Shared;
-internal static class TestExtensions
+public static class AssertionHelpers
{
internal static void AssertNordigenApiResponseIsSuccessful(
NordigenApiResponse response, HttpStatusCode statusCode)
@@ -32,13 +29,26 @@ internal static void AssertNordigenApiResponseIsUnsuccessful(
Assert.That(response.StatusCode, Is.EqualTo(statusCode));
});
}
-
- internal static NordigenClient GetConfiguredClient(string? baseAddress = null)
+
+ internal static void AssertThatAgreementPageContainsAgreement(
+ NordigenApiResponse, BasicResponse> pagedResponse, List ids)
{
- var address = baseAddress ?? NordigenEndpointUrls.Base;
- var httpClient = new HttpClient {BaseAddress = new Uri(address)};
- var secrets = File.ReadAllLines("secrets.txt");
- var credentials = new NordigenClientCredentials(secrets[0], secrets[1]);
- return new NordigenClient(httpClient, credentials);
+ AssertNordigenApiResponseIsSuccessful(pagedResponse, HttpStatusCode.OK);
+ var page2Result = pagedResponse.Result!;
+ var page2Agreements = page2Result.Results.ToList();
+ Assert.Multiple(() =>
+ {
+ Assert.That(page2Agreements, Has.Count.EqualTo(1));
+ Assert.That(ids, Does.Contain(page2Agreements.First().Id.ToString()));
+ });
+ }
+
+ internal static void AssertBasicResponseMatchesExpectations(BasicResponse? response, string summary, string detail)
+ {
+ Assert.Multiple(() =>
+ {
+ Assert.That(response?.Summary, Is.EqualTo(summary));
+ Assert.That(response?.Detail, Is.EqualTo(detail));
+ });
}
}
diff --git a/src/RobinTTY.NordigenApiClient.Tests/Shared/TestHelpers.cs b/src/RobinTTY.NordigenApiClient.Tests/Shared/TestHelpers.cs
new file mode 100644
index 0000000..f59d87a
--- /dev/null
+++ b/src/RobinTTY.NordigenApiClient.Tests/Shared/TestHelpers.cs
@@ -0,0 +1,80 @@
+using System.Text.Json;
+using FakeItEasy;
+using RobinTTY.NordigenApiClient.Endpoints;
+using RobinTTY.NordigenApiClient.JsonConverters;
+using RobinTTY.NordigenApiClient.Models;
+using RobinTTY.NordigenApiClient.Tests.Mocks;
+using RobinTTY.NordigenApiClient.Tests.Mocks.Responses;
+
+namespace RobinTTY.NordigenApiClient.Tests.Shared;
+
+internal static class TestHelpers
+{
+ private static readonly JsonSerializerOptions JsonSerializerOptions;
+ public static readonly string[] Secrets = File.ReadAllLines("secrets.txt");
+ public static MockResponsesModel MockData { get; }
+
+ static TestHelpers()
+ {
+ var json = File.ReadAllText("Mocks/Responses/responses.json");
+ JsonSerializerOptions = new JsonSerializerOptions
+ {
+ Converters =
+ {
+ new JsonWebTokenConverter(), new GuidConverter(),
+ new CultureSpecificDecimalConverter()
+ }
+ };
+ MockData = JsonSerializer.Deserialize(json, JsonSerializerOptions) ??
+ throw new InvalidOperationException("Could not deserialize mock Data");
+ }
+
+ internal static NordigenClient GetConfiguredClient(string? baseAddress = null)
+ {
+ var address = baseAddress ?? NordigenEndpointUrls.Base;
+ var httpClient = new HttpClient();
+ httpClient.BaseAddress = new Uri(address);
+ var credentials = new NordigenClientCredentials(Secrets[0], Secrets[1]);
+ return new NordigenClient(httpClient, credentials);
+ }
+
+ internal static NordigenClient GetMockClient(object value, HttpStatusCode statusCode,
+ bool addDefaultAuthToken = true) => GetMockClient([new ValueTuple