Skip to content

Commit

Permalink
Add StringArrayMergeConverter to process inconsistencies in API respo…
Browse files Browse the repository at this point in the history
…nse model
  • Loading branch information
RobinTTY committed Apr 25, 2024
1 parent 80e702a commit 80e1c61
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ public void Setup()
_apiClient = TestHelpers.GetConfiguredClient();
}

#region RequestsWithSuccessfulResponse

/// <summary>
/// Tests the paging mechanism of retrieving end user agreements.
/// Creates 3 agreements, retrieves them using 3 <see cref="ResponsePage{T}" />s and deletes the agreements after.
/// </summary>
/// <returns></returns>
[Test]
public async Task GetAgreementsPaged()
{
Expand Down Expand Up @@ -78,7 +79,6 @@ public async Task GetAgreementsPaged()
/// <summary>
/// Tests the retrieval of one agreement via <see cref="Guid" /> and string id.
/// </summary>
/// <returns></returns>
[Test]
public async Task GetAgreement()
{
Expand All @@ -104,7 +104,6 @@ public async Task GetAgreement()
/// <summary>
/// Tests the retrieval of an agreement with an invalid guid.
/// </summary>
/// <returns></returns>
[Test]
public async Task GetAgreementWithInvalidGuid()
{
Expand All @@ -118,7 +117,6 @@ public async Task GetAgreementWithInvalidGuid()
/// <summary>
/// Tests the creation and deletion of an end user agreement.
/// </summary>
/// <returns></returns>
[Test]
public async Task CreateAcceptAndDeleteAgreement()
{
Expand Down Expand Up @@ -153,42 +151,70 @@ public async Task CreateAcceptAndDeleteAgreement()
Assert.That(deletionResponse.Result!.Summary, Is.EqualTo("End User Agreement deleted"));
}

#endregion

#region RequestsWithErrors

/// <summary>
/// Tests the retrieving of an end user agreement with an invalid institution id.
/// </summary>
[Test]
public async Task GetAgreementWithInvalidInstitutionId()
{
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}"));
});
}

/// <summary>
/// Tests the creation of an end user agreement with an invalid institution id.
/// </summary>
/// <returns></returns>
[Test]
public async Task CreateAgreementWithInvalidInstitutionId()
{
var agreement = new CreateAgreementRequest(90, 90, ["balances", "details", "transactions"],
"SANDBOXFINANCE_SFIN000");
var agreement = new CreateAgreementRequest(90, 90,
["balances", "details", "transactions"], "invalid_institution");

var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement);
AssertionHelpers.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}"));
});
}

/// <summary>
/// Tests the creation of an end user agreement with invalid parameters.
/// </summary>
/// <returns></returns>
[Test]
public async Task CreateAgreementWithInvalidParams()
{
var agreement = new CreateAgreementRequest(200, 200,
["balances", "details", "transactions", "invalid", "invalid2"], "SANDBOXFINANCE_SFIN0000");
var response = await _apiClient.AgreementsEndpoint.CreateAgreement(agreement);
AssertionHelpers.AssertNordigenApiResponseIsUnsuccessful(response, HttpStatusCode.BadRequest);

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,
Expand All @@ -198,4 +224,25 @@ public async Task CreateAgreementWithInvalidParams()
"max_historical_days must be > 0 and <= SANDBOXFINANCE_SFIN0000 transaction_total_days (90)"));
});
}

[Test]
public async Task CreateAgreementWithInvalidParamsAtPolishInstitution()
{
var agreement = new CreateAgreementRequest(90, 90,
["balances", "transactions"], "PKO_BPKOPLPW");

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.Detail,
Is.EqualTo("For this institution the following scopes are required together: ['details', 'balances']"));
Assert.That(result.Summary, Is.EqualTo("Institution access scope dependencies error"));
});
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace RobinTTY.NordigenApiClient.JsonConverters;
internal class SingleOrArrayConverter<TEnumerable, TItem> : JsonConverter<TEnumerable>
where TEnumerable : IEnumerable<TItem>
{
public override TEnumerable? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
public override TEnumerable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
Expand All @@ -20,6 +20,7 @@ internal class SingleOrArrayConverter<TEnumerable, TItem> : JsonConverter<TEnume
var listItem = JsonSerializer.Deserialize<TItem>(ref reader, options);
if (listItem != null) list.Add(listItem);
}

return (TEnumerable) (IEnumerable<TItem>) list;
default:
var item = JsonSerializer.Deserialize<TItem>(ref reader, options);
Expand All @@ -43,4 +44,4 @@ public override void Write(Utf8JsonWriter writer, TEnumerable value, JsonSeriali
writer.WriteEndArray();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using RobinTTY.NordigenApiClient.Models.Responses;

namespace RobinTTY.NordigenApiClient.JsonConverters;

/// <summary>
/// For some errors the GoCardless API returns arrays for Summary/Detail properties inside the <see cref="BasicResponse"/>.
/// I've never actually seen them contain multiple values, but this converter merges them into one string so that the
/// <see cref="BasicResponse"/> can stay as simple as possible.
/// </summary>
internal class StringArrayMergeConverter : JsonConverter<string>
{
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.Null:
return null;
case JsonTokenType.StartArray:
var list = new List<string>();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray) break;
var listItem = JsonSerializer.Deserialize<string>(ref reader, options);
if (listItem != null) list.Add(listItem);
}

return string.Join("; ", list);
default:
return JsonSerializer.Deserialize<string>(ref reader, options);
}
}

public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value, options);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using RobinTTY.NordigenApiClient.JsonConverters;

namespace RobinTTY.NordigenApiClient.Models.Responses;

Expand All @@ -11,12 +12,14 @@ public class BasicResponse
/// The summary text of the response/error.
/// </summary>
[JsonPropertyName("summary")]
[JsonConverter(typeof(StringArrayMergeConverter))]
public string? Summary { get; init; }

/// <summary>
/// The detailed description of the response/error.
/// </summary>
[JsonPropertyName("detail")]
[JsonConverter(typeof(StringArrayMergeConverter))]
public string? Detail { get; init; }

/// <summary>
Expand Down

0 comments on commit 80e1c61

Please sign in to comment.