From c71ce8371e7a7d606d6ecba2fca918826b1eed77 Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Fri, 3 May 2024 02:08:44 +0200 Subject: [PATCH] Add AccessScope enum, reorder CreateAgreementRequest constructor --- .../Endpoints/AgreementsEndpointTests.cs | 29 ++++++++-------- .../Endpoints/RequisitionsEndpointTests.cs | 3 +- .../Endpoints/AgreementsEndpointTests.cs | 28 +++++++++------- .../Endpoints/AgreementsEndpoint.cs | 5 ++- .../Models/Requests/CreateAgreementRequest.cs | 33 ++++++++++--------- .../Models/Responses/AccessScope.cs | 28 ++++++++++++++++ .../Models/Responses/Agreement.cs | 4 +-- 7 files changed, 83 insertions(+), 47 deletions(-) create mode 100644 src/RobinTTY.NordigenApiClient/Models/Responses/AccessScope.cs diff --git a/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/AgreementsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/AgreementsEndpointTests.cs index e54790a..bcd3786 100644 --- a/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/AgreementsEndpointTests.cs +++ b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/AgreementsEndpointTests.cs @@ -28,8 +28,8 @@ public async Task GetAgreementsPaged() AssertionHelpers.AssertNordigenApiResponseIsSuccessful(existingAgreements, HttpStatusCode.OK); // Create 3 example agreements - var agreementRequest = new CreateAgreementRequest(90, 90, - ["balances", "details", "transactions"], "SANDBOXFINANCE_SFIN0000"); + var agreementRequest = new CreateAgreementRequest("SANDBOXFINANCE_SFIN0000", + [AccessScope.Balances, AccessScope.Details, AccessScope.Transactions]); var ids = new List(); var existingIds = existingAgreements.Result!.Results.Select(agreement => agreement.Id.ToString()).ToList(); @@ -83,8 +83,8 @@ public async Task GetAgreementsPaged() public async Task GetAgreement() { // Create agreement - var agreementRequest = new CreateAgreementRequest(90, 90, - ["balances", "details", "transactions"], "SANDBOXFINANCE_SFIN0000"); + var agreementRequest = new CreateAgreementRequest("SANDBOXFINANCE_SFIN0000", + [AccessScope.Balances, AccessScope.Details, AccessScope.Transactions]); var createResponse = await _apiClient.AgreementsEndpoint.CreateAgreement(agreementRequest); AssertionHelpers.AssertNordigenApiResponseIsSuccessful(createResponse, HttpStatusCode.Created); var id = createResponse.Result!.Id; @@ -108,8 +108,8 @@ public async Task GetAgreement() public async Task CreateAcceptAndDeleteAgreement() { // Create the agreement - var agreement = new CreateAgreementRequest(90, 90, ["balances", "details", "transactions"], - "SANDBOXFINANCE_SFIN0000"); + var agreement = new CreateAgreementRequest("SANDBOXFINANCE_SFIN0000", + [AccessScope.Balances, AccessScope.Details, AccessScope.Transactions]); var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement); AssertionHelpers.AssertNordigenApiResponseIsSuccessful(response, HttpStatusCode.Created); @@ -119,7 +119,9 @@ public async Task CreateAcceptAndDeleteAgreement() Assert.That(result.Id, Is.Not.EqualTo(Guid.Empty)); Assert.That(result.Created - DateTime.UtcNow, Is.AtMost(TimeSpan.FromSeconds(5))); Assert.That(result.Accepted, Is.Null); - Assert.That(result.AccessScope.Except(new[] {"balances", "details", "transactions"}), Is.Empty); + Assert.That( + result.AccessScope.Except([AccessScope.Balances, AccessScope.Details, AccessScope.Transactions]), + Is.Empty); Assert.That(result.MaxHistoricalDays, Is.EqualTo(90)); Assert.That(result.AccessValidForDays, Is.EqualTo(90)); }); @@ -166,8 +168,8 @@ public async Task GetAgreementWithInvalidGuid() [Test] public async Task CreateAgreementWithInvalidInstitutionId() { - var agreement = new CreateAgreementRequest(90, 90, - ["balances", "details", "transactions"], "invalid_institution"); + var agreement = new CreateAgreementRequest("invalid_institution", + [AccessScope.Balances, AccessScope.Details, AccessScope.Transactions]); var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement); @@ -188,7 +190,7 @@ public async Task CreateAgreementWithInvalidInstitutionId() [Test] public async Task CreateAgreementWithEmptyInstitutionIdAndAccessScopes() { - var agreement = new CreateAgreementRequest(90, 90, null!, null!); + var agreement = new CreateAgreementRequest(null!, null!); var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement); @@ -210,8 +212,8 @@ public async Task CreateAgreementWithEmptyInstitutionIdAndAccessScopes() [Test] public async Task CreateAgreementWithInvalidParams() { - var agreement = new CreateAgreementRequest(200, 200, - ["balances", "details", "transactions", "invalid", "invalid2"], "SANDBOXFINANCE_SFIN0000"); + var agreement = new CreateAgreementRequest("SANDBOXFINANCE_SFIN0000", + [AccessScope.Balances, AccessScope.Details, AccessScope.Transactions], 200, 200); var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement); var result = response.Error!; @@ -233,8 +235,7 @@ public async Task CreateAgreementWithInvalidParams() [Test] public async Task CreateAgreementWithInvalidParamsAtPolishInstitution() { - var agreement = new CreateAgreementRequest(90, 90, - ["balances", "transactions"], "PKO_BPKOPLPW"); + var agreement = new CreateAgreementRequest("PKO_BPKOPLPW", [AccessScope.Balances, AccessScope.Transactions]); var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement); diff --git a/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/RequisitionsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/RequisitionsEndpointTests.cs index 58c70dd..1b30b8e 100644 --- a/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/RequisitionsEndpointTests.cs +++ b/src/RobinTTY.NordigenApiClient.Tests/LiveApi/Endpoints/RequisitionsEndpointTests.cs @@ -46,8 +46,7 @@ public async Task GetRequisitionsPaged() const string institutionId = "SANDBOXFINANCE_SFIN0000"; // Create required agreement first - var agreementRequest = new CreateAgreementRequest(90, 90, - new List {"balances", "details", "transactions"}, institutionId); + var agreementRequest = new CreateAgreementRequest(institutionId, [AccessScope.Balances, AccessScope.Details, AccessScope.Transactions]); var agreementResponse = await _apiClient.AgreementsEndpoint.CreateAgreement(agreementRequest); AssertionHelpers.AssertNordigenApiResponseIsSuccessful(agreementResponse, HttpStatusCode.Created); var agreementId = agreementResponse.Result!.Id; diff --git a/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/AgreementsEndpointTests.cs b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/AgreementsEndpointTests.cs index d5d2f8b..543bc04 100644 --- a/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/AgreementsEndpointTests.cs +++ b/src/RobinTTY.NordigenApiClient.Tests/Mocks/Endpoints/AgreementsEndpointTests.cs @@ -1,5 +1,6 @@ using FakeItEasy; using RobinTTY.NordigenApiClient.Models.Requests; +using RobinTTY.NordigenApiClient.Models.Responses; using RobinTTY.NordigenApiClient.Tests.Shared; namespace RobinTTY.NordigenApiClient.Tests.Mocks.Endpoints; @@ -42,7 +43,7 @@ public async Task GetAgreements() Assert.That(responseAgreement.AccessValidForDays, Is.EqualTo(90)); Assert.That(responseAgreement.AccessScope, Has.Count.EqualTo(3)); - var expectedAccessScopes = new[] {"balances", "details", "transactions"}; + var expectedAccessScopes = new[] {AccessScope.Balances, AccessScope.Details, AccessScope.Transactions}; Assert.That(responseAgreement.AccessScope, Is.EqualTo(expectedAccessScopes)); }); } @@ -69,7 +70,7 @@ public async Task GetAgreement() 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"}; + var expectedAccessScopes = new[] {AccessScope.Balances, AccessScope.Details, AccessScope.Transactions}; Assert.That(agreement.Result!.AccessScope, Is.EqualTo(expectedAccessScopes)); }); } @@ -83,8 +84,8 @@ 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 agreementRequest = new CreateAgreementRequest("SANDBOXFINANCE_SFIN0000", + [AccessScope.Balances, AccessScope.Details, AccessScope.Transactions], 145, 145); var createResponse = await apiClient.AgreementsEndpoint.CreateAgreement(agreementRequest); AssertionHelpers.AssertNordigenApiResponseIsSuccessful(createResponse, HttpStatusCode.Created); @@ -95,7 +96,7 @@ public async Task CreateAgreement() Assert.That(createResponse.Result!.MaxHistoricalDays, Is.EqualTo(145)); Assert.That(createResponse.Result!.AccessValidForDays, Is.EqualTo(145)); - var expectedAccessScopes = new[] {"balances", "details", "transactions"}; + var expectedAccessScopes = new[] {AccessScope.Balances, AccessScope.Details, AccessScope.Transactions}; Assert.That(createResponse.Result!.AccessScope, Is.EqualTo(expectedAccessScopes)); }); } @@ -153,8 +154,8 @@ 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 agreement = new CreateAgreementRequest("invalid_institution", + [AccessScope.Balances, AccessScope.Details, AccessScope.Transactions]); var response = await apiClient.AgreementsEndpoint.CreateAgreement(agreement); @@ -178,7 +179,7 @@ public async Task CreateAgreementWithEmptyInstitutionIdAndAccessScopes() var apiClient = TestHelpers.GetMockClient( TestHelpers.MockData.AgreementsEndpointMockData.CreateAgreementWithEmptyInstitutionIdAndAccessScopes, HttpStatusCode.BadRequest); - var agreement = new CreateAgreementRequest(90, 90, null!, null!); + var agreement = new CreateAgreementRequest(null!, null!, null, null); var response = await apiClient.AgreementsEndpoint.CreateAgreement(agreement); @@ -191,6 +192,10 @@ public async Task CreateAgreementWithEmptyInstitutionIdAndAccessScopes() 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.")); + + Assert.That(response.Error.AccessValidForDaysError, Is.Null); + Assert.That(response.Error.MaxHistoricalDaysError, Is.Null); + Assert.That(response.Error.AgreementError, Is.Null); }); } @@ -203,8 +208,8 @@ 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 agreement = new CreateAgreementRequest("SANDBOXFINANCE_SFIN0000", + [AccessScope.Balances, AccessScope.Details, AccessScope.Transactions], 200, 200); var response = await apiClient.AgreementsEndpoint.CreateAgreement(agreement); var result = response.Error!; @@ -229,8 +234,7 @@ 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 agreement = new CreateAgreementRequest("PKO_BPKOPLPW", [AccessScope.Balances, AccessScope.Transactions]); var response = await apiClient.AgreementsEndpoint.CreateAgreement(agreement); diff --git a/src/RobinTTY.NordigenApiClient/Endpoints/AgreementsEndpoint.cs b/src/RobinTTY.NordigenApiClient/Endpoints/AgreementsEndpoint.cs index bce2efd..c104e6c 100644 --- a/src/RobinTTY.NordigenApiClient/Endpoints/AgreementsEndpoint.cs +++ b/src/RobinTTY.NordigenApiClient/Endpoints/AgreementsEndpoint.cs @@ -1,4 +1,6 @@ using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Serialization; using RobinTTY.NordigenApiClient.Contracts; using RobinTTY.NordigenApiClient.Models.Errors; using RobinTTY.NordigenApiClient.Models.Requests; @@ -41,7 +43,8 @@ public async Task> GetAgreement(st public async Task> CreateAgreement( CreateAgreementRequest agreement, CancellationToken cancellationToken = default) { - var body = JsonContent.Create(agreement); + var body = JsonContent.Create(agreement, + options: new JsonSerializerOptions {DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull}); return await _nordigenClient.MakeRequest( NordigenEndpointUrls.AgreementsEndpoint, HttpMethod.Post, cancellationToken, body: body); } diff --git a/src/RobinTTY.NordigenApiClient/Models/Requests/CreateAgreementRequest.cs b/src/RobinTTY.NordigenApiClient/Models/Requests/CreateAgreementRequest.cs index ad598ea..ff1c4c4 100644 --- a/src/RobinTTY.NordigenApiClient/Models/Requests/CreateAgreementRequest.cs +++ b/src/RobinTTY.NordigenApiClient/Models/Requests/CreateAgreementRequest.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using RobinTTY.NordigenApiClient.Models.Responses; namespace RobinTTY.NordigenApiClient.Models.Requests; @@ -8,6 +9,18 @@ namespace RobinTTY.NordigenApiClient.Models.Requests; /// public class CreateAgreementRequest { + /// + /// The institution this agreement refers to. + /// + [JsonPropertyName("institution_id")] + public string InstitutionId { get; set; } + + /// + /// The scope of information that can be accessed. + /// + [JsonPropertyName("access_scope")] + public List AccessScope { get; set; } + /// /// The length of the transaction history in days. /// @@ -20,28 +33,16 @@ public class CreateAgreementRequest [JsonPropertyName("access_valid_for_days")] public uint AccessValidForDays { get; set; } - /// - /// The scope of information that can be accessed. - /// - [JsonPropertyName("access_scope")] - public List AccessScope { get; set; } - - /// - /// The institution this agreement refers to. - /// - [JsonPropertyName("institution_id")] - public string InstitutionId { get; set; } - /// /// Creates a new instance of . /// + /// The institution this agreement refers to. + /// The scope of information that can be accessed. /// The length of the transaction history in days. /// The length the access to the account will be valid for. - /// The scope of information that can be accessed. - /// The institution this agreement refers to. [JsonConstructor] - public CreateAgreementRequest(uint maxHistoricalDays, uint accessValidForDays, List accessScope, - string institutionId) + public CreateAgreementRequest(string institutionId, List accessScope, uint maxHistoricalDays = 90, + uint accessValidForDays = 90) { MaxHistoricalDays = maxHistoricalDays; AccessValidForDays = accessValidForDays; diff --git a/src/RobinTTY.NordigenApiClient/Models/Responses/AccessScope.cs b/src/RobinTTY.NordigenApiClient/Models/Responses/AccessScope.cs new file mode 100644 index 0000000..880d901 --- /dev/null +++ b/src/RobinTTY.NordigenApiClient/Models/Responses/AccessScope.cs @@ -0,0 +1,28 @@ +namespace RobinTTY.NordigenApiClient.Models.Responses; + +/// +/// Access scopes can be used to limit the access to the 3 major data blocks the GoCardless API is offering. +/// This feature is not supported by all banks, check to verify if it is. +/// +public enum AccessScope +{ + /// + /// An undefined access scope. Assigned if the scope couldn't be matched to any known types. + /// + Undefined, + + /// + /// Access scope required to access the balances of an account. + /// + Balances, + + /// + /// Access scope required to access the transactions of an account. + /// + Transactions, + + /// + /// Access scope needed to access account details. + /// + Details +} diff --git a/src/RobinTTY.NordigenApiClient/Models/Responses/Agreement.cs b/src/RobinTTY.NordigenApiClient/Models/Responses/Agreement.cs index dbbe1ff..23a7a17 100644 --- a/src/RobinTTY.NordigenApiClient/Models/Responses/Agreement.cs +++ b/src/RobinTTY.NordigenApiClient/Models/Responses/Agreement.cs @@ -40,12 +40,12 @@ public class Agreement : CreateAgreementRequest public Agreement( uint maxHistoricalDays, uint accessValidForDays, - List accessScope, + List accessScope, string institutionId, Guid id, DateTime created, DateTime? accepted - ) : base(maxHistoricalDays, accessValidForDays, accessScope, institutionId) + ) : base(institutionId, accessScope, maxHistoricalDays, accessValidForDays) { Id = id; Created = created;