feat: implement transaction handling and add integration tests for Plaid transaction handlers
This commit is contained in:
@@ -16,6 +16,45 @@ public abstract class IntegrationTest : IAsyncLifetime
|
||||
AppDbContext = new(options, _fileSystem);
|
||||
}
|
||||
|
||||
protected async Task<User> 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;
|
||||
}
|
||||
|
||||
public async ValueTask InitializeAsync()
|
||||
{
|
||||
await AppDbContext.Database.MigrateAsync();
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
namespace FiscalOS.Infra.Tests.Integration;
|
||||
|
||||
public class PlaidAddedTransactionHandlerTests : IntegrationTest
|
||||
{
|
||||
private readonly Mock<ILogger<PlaidAddedTransactionHandler>> _mockLogger = new();
|
||||
private readonly PlaidAddedTransactionHandler _sut;
|
||||
|
||||
public PlaidAddedTransactionHandlerTests()
|
||||
{
|
||||
_sut = PlaidAddedTransactionHandler.From(_mockLogger.Object, AppDbContext);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_WhenCalledWithNewTransaction_ItShouldAddTheTransaction()
|
||||
{
|
||||
var user = await CreateTestUserAsync();
|
||||
var account = user.Accounts.First();
|
||||
|
||||
var transaction = new Going.Plaid.Entity.Transaction()
|
||||
{
|
||||
TransactionId = Guid.NewGuid().ToString(),
|
||||
MerchantName = "Test Merchant",
|
||||
OriginalDescription = "Test transaction",
|
||||
Amount = 100,
|
||||
Datetime = DateTimeOffset.UtcNow,
|
||||
Pending = false,
|
||||
};
|
||||
|
||||
var result = await _sut.HandleAsync(account, [transaction], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Should().Be(1);
|
||||
account.Transactions.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_WhenCalledWithPendingTransaction_ItShouldSkipTheTransaction()
|
||||
{
|
||||
var user = await CreateTestUserAsync();
|
||||
var account = user.Accounts.First();
|
||||
|
||||
var transaction = new Going.Plaid.Entity.Transaction()
|
||||
{
|
||||
TransactionId = Guid.NewGuid().ToString(),
|
||||
MerchantName = "Test Merchant",
|
||||
OriginalDescription = "Test transaction",
|
||||
Amount = 100,
|
||||
Datetime = DateTimeOffset.UtcNow,
|
||||
Pending = true,
|
||||
};
|
||||
|
||||
var result = await _sut.HandleAsync(account, [transaction], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Should().Be(1);
|
||||
account.Transactions.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_WhenCalledWithExistingTransaction_ItShouldNotAddTheTransactionToTheDatabase()
|
||||
{
|
||||
var user = await CreateTestUserAsync();
|
||||
var account = user.Accounts.First();
|
||||
var existingTransaction = user.Transactions.First();
|
||||
|
||||
var transaction = new Going.Plaid.Entity.Transaction()
|
||||
{
|
||||
TransactionId = ((PlaidTransactionMetadata)existingTransaction.Metadata!).PlaidId,
|
||||
MerchantName = "Test Merchant",
|
||||
OriginalDescription = "Test transaction",
|
||||
Amount = 100,
|
||||
Datetime = DateTimeOffset.UtcNow,
|
||||
Pending = false,
|
||||
};
|
||||
|
||||
var result = await _sut.HandleAsync(account, [transaction], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Should().Be(1);
|
||||
account.Transactions.Should().HaveCount(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace FiscalOS.Infra.Tests.Integration;
|
||||
|
||||
public class PlaidModifiedTransactionHandlerTests : IntegrationTest
|
||||
{
|
||||
private readonly Mock<ILogger<PlaidModifiedTransactionHandler>> _mockLogger = new();
|
||||
private readonly PlaidModifiedTransactionHandler _sut;
|
||||
|
||||
public PlaidModifiedTransactionHandlerTests()
|
||||
{
|
||||
_sut = PlaidModifiedTransactionHandler.From(_mockLogger.Object, AppDbContext);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_WhenCalledWithExistingTransaction_ItShouldUpdateTheTransaction()
|
||||
{
|
||||
var user = await CreateTestUserAsync();
|
||||
var account = user.Accounts.First();
|
||||
var existingTransaction = account.Transactions.First();
|
||||
|
||||
var transaction = new Going.Plaid.Entity.Transaction()
|
||||
{
|
||||
TransactionId = ((PlaidTransactionMetadata)existingTransaction.Metadata!).PlaidId,
|
||||
MerchantName = Guid.NewGuid().ToString(),
|
||||
OriginalDescription = "Test transaction",
|
||||
Amount = 100,
|
||||
Datetime = DateTimeOffset.UtcNow,
|
||||
Pending = false,
|
||||
};
|
||||
|
||||
var result = await _sut.HandleAsync(account, [transaction], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Should().Be(1);
|
||||
existingTransaction.MerchantName.Should().Be(transaction.MerchantName);
|
||||
}
|
||||
}
|
||||
@@ -13,43 +13,12 @@ public class PlaidRemovedTransactionHandlerTests : IntegrationTest
|
||||
[Fact]
|
||||
public async Task HandleAsync_WhenCalled_ItShouldRemoveTheCorrectTransactions()
|
||||
{
|
||||
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);
|
||||
var user = await CreateTestUserAsync();
|
||||
var testTransaction = user.Transactions.First();
|
||||
|
||||
var transactionToRemove = new Going.Plaid.Entity.RemovedTransaction()
|
||||
{
|
||||
TransactionId = transactionMetadata.PlaidId,
|
||||
TransactionId = ((PlaidTransactionMetadata)testTransaction.Metadata!).PlaidId,
|
||||
};
|
||||
|
||||
var result = await _sut.HandleAsync([transactionToRemove], TestContext.Current.CancellationToken);
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
using Going.Plaid.Transactions;
|
||||
|
||||
namespace FiscalOS.Infra.Tests.Unit;
|
||||
|
||||
public class PlaidTransactionProcessorTests
|
||||
{
|
||||
private readonly Mock<IPlaidAddedTransactionHandler> _mockAddedHandler = new();
|
||||
private readonly Mock<IPlaidModifiedTransactionHandler> _mockModifiedHandler = new();
|
||||
private readonly Mock<IPlaidRemovedTransactionHandler> _mockRemovedHandler = new();
|
||||
private readonly PlaidTransactionProcessor _sut;
|
||||
|
||||
public PlaidTransactionProcessorTests()
|
||||
{
|
||||
_sut = PlaidTransactionProcessor.From(
|
||||
_mockAddedHandler.Object,
|
||||
_mockModifiedHandler.Object,
|
||||
_mockRemovedHandler.Object
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, false)]
|
||||
[InlineData(1, 1, 1, true)]
|
||||
[InlineData(0, 1, 1, false)]
|
||||
[InlineData(1, 0, 1, false)]
|
||||
[InlineData(1, 1, 0, false)]
|
||||
public async Task ProcessAsync_WhenCalledAndTransactionsAreHandled_ItShouldReturnCorrectResult(
|
||||
int numAdded,
|
||||
int numModified,
|
||||
int numRemoved,
|
||||
bool expectedResult
|
||||
)
|
||||
{
|
||||
_mockAddedHandler
|
||||
.Setup(
|
||||
static m => m.HandleAsync(
|
||||
It.IsAny<Account>(),
|
||||
It.IsAny<IEnumerable<Going.Plaid.Entity.Transaction>>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
)
|
||||
)
|
||||
.ReturnsAsync(numAdded);
|
||||
|
||||
_mockModifiedHandler
|
||||
.Setup(
|
||||
static m => m.HandleAsync(
|
||||
It.IsAny<Account>(),
|
||||
It.IsAny<IEnumerable<Going.Plaid.Entity.Transaction>>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
)
|
||||
)
|
||||
.ReturnsAsync(numModified);
|
||||
|
||||
_mockRemovedHandler
|
||||
.Setup(
|
||||
static m => m.HandleAsync(
|
||||
It.IsAny<IEnumerable<Going.Plaid.Entity.RemovedTransaction>>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
)
|
||||
)
|
||||
.ReturnsAsync(numRemoved);
|
||||
|
||||
var metadata = PlaidAccountMetadata.From("plaidId", "plaidName");
|
||||
var account = Account.From("name", metadata);
|
||||
var syncResponse = new TransactionsSyncResponse()
|
||||
{
|
||||
Added = [new()],
|
||||
Modified = [new()],
|
||||
Removed = [new()],
|
||||
};
|
||||
|
||||
var result = await _sut.ProcessAsync(account, syncResponse, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Should().Be(expectedResult);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace FiscalOS.Infra.Tests.Unit;
|
||||
|
||||
public class PlaidTransactionSyncerTests
|
||||
{
|
||||
private readonly Mock<ILogger<PlaidTransactionSyncer>> _mockSyncer = new();
|
||||
private
|
||||
private readonly PlaidTransactionSyncer _sut;
|
||||
|
||||
public PlaidTransactionSyncer()
|
||||
{
|
||||
_sut = PlaidTransactionSyncer.From();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user