From 659b2bbf191d6c6f0b667f70b068c9bcb0e7ea74 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Fri, 6 Mar 2026 07:14:57 -0600 Subject: [PATCH] tests(infra.tests): wip on builder apis for test data --- src/FiscalOS.Core/Accounts/Institution.cs | 7 ++ .../Data/AccountBuilder.cs | 40 +++++++ .../Data/AccountMetadataBuilder.cs | 33 ++++++ .../Data/InstitutionBuilder.cs | 67 ++++++++++++ .../Data/InstitutionMetadataBuilder.cs | 52 +++++++++ .../FiscalOS.Infra.Tests/Data/UserBuilder.cs | 102 ++++++++++++++++++ .../Integration/IntegrationTest.cs | 27 +++-- 7 files changed, 313 insertions(+), 15 deletions(-) create mode 100644 tests/FiscalOS.Infra.Tests/Data/AccountBuilder.cs create mode 100644 tests/FiscalOS.Infra.Tests/Data/AccountMetadataBuilder.cs create mode 100644 tests/FiscalOS.Infra.Tests/Data/InstitutionBuilder.cs create mode 100644 tests/FiscalOS.Infra.Tests/Data/InstitutionMetadataBuilder.cs create mode 100644 tests/FiscalOS.Infra.Tests/Data/UserBuilder.cs diff --git a/src/FiscalOS.Core/Accounts/Institution.cs b/src/FiscalOS.Core/Accounts/Institution.cs index 60984dd..5d09ebb 100644 --- a/src/FiscalOS.Core/Accounts/Institution.cs +++ b/src/FiscalOS.Core/Accounts/Institution.cs @@ -15,12 +15,19 @@ public sealed class Institution : Entity } public static Institution From(string? name, InstitutionMetadata metadata) + { + return From(null, name, metadata); + } + + public static Institution From(User? user, string? name, InstitutionMetadata metadata) { ArgumentNullException.ThrowIfNull(name, nameof(name)); ArgumentNullException.ThrowIfNull(metadata, nameof(metadata)); return new Institution() { + UserId = user?.Id ?? Guid.Empty, + User = user, Name = name, Metadata = metadata }; diff --git a/tests/FiscalOS.Infra.Tests/Data/AccountBuilder.cs b/tests/FiscalOS.Infra.Tests/Data/AccountBuilder.cs new file mode 100644 index 0000000..4d641b3 --- /dev/null +++ b/tests/FiscalOS.Infra.Tests/Data/AccountBuilder.cs @@ -0,0 +1,40 @@ +namespace FiscalOS.Infra.Tests.Data; + +internal sealed class AccountBuilder +{ + private string _name = "accountName"; + private AccountMetadata? _metadata; + + private AccountBuilder() + { + } + + public static AccountBuilder Create() + { + return new(); + } + + public AccountBuilder WithName(string name) + { + _name = name; + return this; + } + + public AccountBuilder WithMetadata(Action? action = null) + { + var imb = AccountMetadataBuilder.Create(); + action?.Invoke(imb); + _metadata = imb.Build(); + return this; + } + + public Account Build() + { + if (_metadata is null) + { + throw new InvalidOperationException($"You must call {nameof(WithMetadata)} prior to building"); + } + + return Account.From(_name, _metadata); + } +} \ No newline at end of file diff --git a/tests/FiscalOS.Infra.Tests/Data/AccountMetadataBuilder.cs b/tests/FiscalOS.Infra.Tests/Data/AccountMetadataBuilder.cs new file mode 100644 index 0000000..7e7574f --- /dev/null +++ b/tests/FiscalOS.Infra.Tests/Data/AccountMetadataBuilder.cs @@ -0,0 +1,33 @@ +namespace FiscalOS.Infra.Tests.Data; + +internal sealed class AccountMetadataBuilder +{ + private string _plaidId = "plaidId"; + private string _plaidName = "plaidName"; + + private AccountMetadataBuilder() + { + } + + public static AccountMetadataBuilder Create() + { + return new(); + } + + public AccountMetadataBuilder WithPlaidId(string plaidId) + { + _plaidId = plaidId; + return this; + } + + public AccountMetadataBuilder WithPlaidName(string plaidName) + { + _plaidName = plaidName; + return this; + } + + public PlaidAccountMetadata Build() + { + return PlaidAccountMetadata.From(_plaidId, _plaidName); + } +} \ No newline at end of file diff --git a/tests/FiscalOS.Infra.Tests/Data/InstitutionBuilder.cs b/tests/FiscalOS.Infra.Tests/Data/InstitutionBuilder.cs new file mode 100644 index 0000000..a8558e3 --- /dev/null +++ b/tests/FiscalOS.Infra.Tests/Data/InstitutionBuilder.cs @@ -0,0 +1,67 @@ +namespace FiscalOS.Infra.Tests.Data; + +internal sealed class InstitutionBuilder +{ + private User? _user; + private string _name = "institutionName"; + private InstitutionMetadata? _metadata; + private readonly List _accounts = []; + + private InstitutionBuilder() + { + } + + public static InstitutionBuilder Create() + { + return new(); + } + + public InstitutionBuilder WithUser(User user) + { + _user = user; + return this; + } + + public InstitutionBuilder WithName(string name) + { + _name = name; + return this; + } + + public InstitutionBuilder WithMetadata(Action? action = null) + { + var imb = InstitutionMetadataBuilder.Create(); + action?.Invoke(imb); + _metadata = imb.Build(); + return this; + } + + public InstitutionBuilder WithAccount(Action? action = null) + { + var ab = AccountBuilder.Create(); + action?.Invoke(ab); + _accounts.Add(ab.Build()); + return this; + } + + public Institution Build() + { + if (_metadata is null) + { + throw new InvalidOperationException($"You must call {nameof(WithMetadata)} prior to building"); + } + + var institution = Institution.From( + _user, + _name, + _metadata + ); + + foreach (var account in _accounts) + { + institution.AddAccount(account); + } + + return institution; + } +} \ No newline at end of file diff --git a/tests/FiscalOS.Infra.Tests/Data/InstitutionMetadataBuilder.cs b/tests/FiscalOS.Infra.Tests/Data/InstitutionMetadataBuilder.cs new file mode 100644 index 0000000..4a7a1c6 --- /dev/null +++ b/tests/FiscalOS.Infra.Tests/Data/InstitutionMetadataBuilder.cs @@ -0,0 +1,52 @@ +namespace FiscalOS.Infra.Tests.Data; + +internal sealed class InstitutionMetadataBuilder +{ + private string _plaidId = "plaidId"; + private string _plaidName = "plaidName"; + private string _accessToken = "encryptedAccessToken"; + private string _itemId = "itemId"; + + private InstitutionMetadataBuilder() + { + } + + public static InstitutionMetadataBuilder Create() + { + return new(); + } + + public InstitutionMetadataBuilder WithPlaidId(string plaidId) + { + _plaidId = plaidId; + return this; + } + + public InstitutionMetadataBuilder WithPlaidName(string plaidName) + { + _plaidName = plaidName; + return this; + } + + public InstitutionMetadataBuilder WithAccessToken(string accessToken) + { + _accessToken = accessToken; + return this; + } + + public InstitutionMetadataBuilder WithItemId(string itemId) + { + _itemId = itemId; + return this; + } + + public PlaidInstitutionMetadata Build() + { + return PlaidInstitutionMetadata.From( + _plaidId, + _plaidName, + _accessToken, + _itemId + ); + } +} \ No newline at end of file diff --git a/tests/FiscalOS.Infra.Tests/Data/UserBuilder.cs b/tests/FiscalOS.Infra.Tests/Data/UserBuilder.cs new file mode 100644 index 0000000..8483c9b --- /dev/null +++ b/tests/FiscalOS.Infra.Tests/Data/UserBuilder.cs @@ -0,0 +1,102 @@ +namespace FiscalOS.Infra.Tests.Data; + +// protected async Task CreateTestUserAsync() +// { +// var user = User.From( +// "username", +// "hashedPassword", +// EncryptedDataKey.From("keyUsed", "encryptedKey") +// ); +// +// var institutionMetadata = PlaidInstitutionMetadata.From( +// "plaidId", +// "plaidName", +// "encryptedAccessToken", +// "itemId" +// ); +// var institution = Institution.From("institutionName", institutionMetadata); +// user.AddInstitution(institution); +// +// var accountMetadata = PlaidAccountMetadata.From("plaidId", "plaidName"); +// var account = Account.From("some account", accountMetadata); +// user.AddAccount(account); +// institution.AddAccount(account); +// +// var transactionMetadata = PlaidTransactionMetadata.From("plaidId"); +// var transaction = Transaction.From( +// "merchantName", +// "description", +// 100, +// DateTimeOffset.UtcNow, +// transactionMetadata +// ); +// user.AddTransaction(transaction); +// account.AddTransaction(transaction); +// +// await AppDbContext.AddAsync(user, TestContext.Current.CancellationToken); +// await AppDbContext.SaveChangesAsync(TestContext.Current.CancellationToken); +// +// return user; +// } +internal sealed class UserBuilder +{ + private string _username = "username"; + private string _password = "hashedPassword"; + private EncryptedDataKey _dataKey = EncryptedDataKey.From( + "keyUsed", + "encryptedKey" + ); + private readonly List _instituionBuilders = []; + + private UserBuilder() + { + } + + public static UserBuilder Create() + { + return new(); + } + + public UserBuilder WithUsername(string username) + { + _username = username; + return this; + } + + public UserBuilder WithPassword(string password) + { + _password = password; + return this; + } + + public UserBuilder WithDataKey(EncryptedDataKey key) + { + _dataKey = key; + return this; + } + + public UserBuilder WithInstitution(Action? action = null) + { + var ib = InstitutionBuilder.Create(); + action?.Invoke(ib); + _instituionBuilders.Add(ib); + return this; + } + + public User Build() + { + var user = User.From( + _username, + _password, + _dataKey + ); + + foreach (var ib in _instituionBuilders) + { + var institution = ib.WithUser(user).Build(); + user.AddInstitution(institution); + } + + return user; + } +} \ No newline at end of file diff --git a/tests/FiscalOS.Infra.Tests/Integration/IntegrationTest.cs b/tests/FiscalOS.Infra.Tests/Integration/IntegrationTest.cs index d741651..a56953c 100644 --- a/tests/FiscalOS.Infra.Tests/Integration/IntegrationTest.cs +++ b/tests/FiscalOS.Infra.Tests/Integration/IntegrationTest.cs @@ -1,3 +1,5 @@ +using FiscalOS.Infra.Tests.Data; + namespace FiscalOS.Infra.Tests.Integration; public abstract class IntegrationTest : IAsyncLifetime @@ -18,25 +20,20 @@ public abstract class IntegrationTest : IAsyncLifetime protected async Task CreateTestUserAsync() { - var user = User.From( - "username", - "hashedPassword", - EncryptedDataKey.From("keyUsed", "encryptedKey") - ); - - var institutionMetadata = PlaidInstitutionMetadata.From( - "plaidId", - "plaidName", - "encryptedAccessToken", - "itemId" - ); - var institution = Institution.From("institutionName", institutionMetadata); - user.AddInstitution(institution); + var user = UserBuilder.Create() + .WithInstitution(static ib => + { + ib.WithMetadata(); + ib.WithAccount(static ab => + { + ab.WithMetadata(); + }); + }) + .Build(); var accountMetadata = PlaidAccountMetadata.From("plaidId", "plaidName"); var account = Account.From("some account", accountMetadata); user.AddAccount(account); - institution.AddAccount(account); var transactionMetadata = PlaidTransactionMetadata.From("plaidId"); var transaction = Transaction.From(