From 3b7830b1bc2b9315ac60719c28f3a86b20e8a488 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Fri, 13 Feb 2026 21:32:49 -0600 Subject: [PATCH] refactor(api): introduce DTOs for institutional account discovery - Replace raw Plaid entity response with a dedicated Response wrapper - Map Plaid accounts to AccountDto to decouple API from external SDK models - Update integration tests to assert against the new response structure - Add JsonConstructors to DTO records for reliable deserialization --- .../Institutions/GetAvailable/Endpoint.cs | 3 +- .../Institutions/GetAvailable/Response.cs | 39 +++++++++++++++++++ .../Institutions/GetAvailableTests.cs | 6 ++- 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 src/FiscalOS.API/Institutions/GetAvailable/Response.cs diff --git a/src/FiscalOS.API/Institutions/GetAvailable/Endpoint.cs b/src/FiscalOS.API/Institutions/GetAvailable/Endpoint.cs index 5401082..18e4c86 100644 --- a/src/FiscalOS.API/Institutions/GetAvailable/Endpoint.cs +++ b/src/FiscalOS.API/Institutions/GetAvailable/Endpoint.cs @@ -44,7 +44,8 @@ internal static class Endpoint var accessToken = await encryptor.DecryptAsyncFor(user, plaidMetadata.EncryptedAccessToken, ct); var accounts = await plaidService.GetAccountsAsync(accessToken); + var accountsDtos = accounts.Select(AccountDto.FromPlaidAccount); - return Results.Ok(accounts); + return Results.Ok(Response.From(accountsDtos)); } } \ No newline at end of file diff --git a/src/FiscalOS.API/Institutions/GetAvailable/Response.cs b/src/FiscalOS.API/Institutions/GetAvailable/Response.cs new file mode 100644 index 0000000..b1afc7d --- /dev/null +++ b/src/FiscalOS.API/Institutions/GetAvailable/Response.cs @@ -0,0 +1,39 @@ +namespace FiscalOS.API.Institutions.GetAvailable; + +internal sealed record Response +{ + public IEnumerable Accounts { get; init; } = []; + + [JsonConstructor] + private Response() + { + } + + public static Response From(IEnumerable accounts) + { + return new Response + { + Accounts = accounts + }; + } +} + +internal sealed record AccountDto +{ + public string ProviderId { get; init; } = string.Empty; + public string ProviderName { get; init; } = string.Empty; + + [JsonConstructor] + private AccountDto() + { + } + + public static AccountDto FromPlaidAccount(Going.Plaid.Entity.Account plaidAccount) + { + return new AccountDto + { + ProviderId = plaidAccount.AccountId, + ProviderName = plaidAccount.Name + }; + } +} \ No newline at end of file diff --git a/tests/FiscalOS.API.Tests/Integration/Institutions/GetAvailableTests.cs b/tests/FiscalOS.API.Tests/Integration/Institutions/GetAvailableTests.cs index 95e069e..a258fd5 100644 --- a/tests/FiscalOS.API.Tests/Integration/Institutions/GetAvailableTests.cs +++ b/tests/FiscalOS.API.Tests/Integration/Institutions/GetAvailableTests.cs @@ -1,3 +1,5 @@ +using FiscalOS.API.Institutions.GetAvailable; + using Institution = FiscalOS.Core.Accounts.Institution; namespace FiscalOS.API.Tests.Integration.Institutions; @@ -108,7 +110,7 @@ public class GetAvailableTests(TestApi testApi) : IntegrationTest(testApi) var response = await Client.SendAsync(request, TestContext.Current.CancellationToken); - (await response.Should().BeJsonContentOfType>(HttpStatusCode.OK)) - .Which.Should().BeEquivalentTo(expectedAccounts); + (await response.Should().BeJsonContentOfType(HttpStatusCode.OK)) + .Which.Accounts.Should().BeEquivalentTo(expectedAccounts.Select(AccountDto.FromPlaidAccount)); } } \ No newline at end of file