using MoneyMap.Models; using MoneyMap.Services; using MoneyMap.Tests.TestHelpers; namespace MoneyMap.Tests.Services; public class ReceiptMatchingServiceTests { [Fact] public async Task GetTransactionIdsWithReceiptsAsync_ReturnsTransactionIds() { // Arrange using var context = DbContextHelper.CreateInMemoryContext(); var service = new ReceiptMatchingService(context); var account = new Account { Id = 1, Institution = "Test Bank", AccountType = AccountType.Checking, Last4 = "1234", Owner = "Test Owner" }; context.Accounts.Add(account); var transaction1 = new Transaction { Id = 1, Date = DateTime.Now, Amount = -50.00m, Name = "Store A", Memo = "Test", AccountId = 1 }; var transaction2 = new Transaction { Id = 2, Date = DateTime.Now, Amount = -30.00m, Name = "Store B", Memo = "Test", AccountId = 1 }; context.Transactions.AddRange(transaction1, transaction2); var receipt = new Receipt { Id = 1, TransactionId = 1, FileName = "receipt.pdf", ContentType = "application/pdf", StoragePath = "/receipts/receipt.pdf", FileSizeBytes = 1024, FileHashSha256 = "hash123", UploadedAtUtc = DateTime.UtcNow }; context.Receipts.Add(receipt); await context.SaveChangesAsync(); // Act var result = await service.GetTransactionIdsWithReceiptsAsync(); // Assert Assert.Single(result); Assert.Contains(1L, result); Assert.DoesNotContain(2L, result); } [Fact] public async Task FindMatchingTransactionsAsync_FiltersByDateRange() { // Arrange using var context = DbContextHelper.CreateInMemoryContext(); var service = new ReceiptMatchingService(context); var account = new Account { Id = 1, Institution = "Test Bank", AccountType = AccountType.Checking, Last4 = "1234", Owner = "Test Owner" }; context.Accounts.Add(account); var receiptDate = new DateTime(2025, 1, 15); var withinRange = new Transaction { Id = 1, Date = receiptDate.AddDays(2), // Within +/- 3 days Amount = -50.00m, Name = "Store A", Memo = "Test", AccountId = 1 }; var outsideRange = new Transaction { Id = 2, Date = receiptDate.AddDays(5), // Outside +/- 3 days Amount = -50.00m, Name = "Store B", Memo = "Test", AccountId = 1 }; context.Transactions.AddRange(withinRange, outsideRange); await context.SaveChangesAsync(); var criteria = new ReceiptMatchCriteria { ReceiptDate = receiptDate, ExcludeTransactionIds = new HashSet() }; // Act var result = await service.FindMatchingTransactionsAsync(criteria); // Assert Assert.Single(result); Assert.Equal(1, result[0].Id); } [Fact] public async Task FindMatchingTransactionsAsync_FiltersByAmountTolerance() { // Arrange using var context = DbContextHelper.CreateInMemoryContext(); var service = new ReceiptMatchingService(context); var account = new Account { Id = 1, Institution = "Test Bank", AccountType = AccountType.Checking, Last4 = "1234", Owner = "Test Owner" }; context.Accounts.Add(account); var receiptDate = DateTime.Now; var receiptTotal = 100.00m; var withinTolerance = new Transaction { Id = 1, Date = receiptDate, Amount = -105.00m, // Within 10% tolerance Name = "Store A", Memo = "Test", AccountId = 1 }; var outsideTolerance = new Transaction { Id = 2, Date = receiptDate, Amount = -150.00m, // Outside 10% tolerance Name = "Store B", Memo = "Test", AccountId = 1 }; context.Transactions.AddRange(withinTolerance, outsideTolerance); await context.SaveChangesAsync(); var criteria = new ReceiptMatchCriteria { ReceiptDate = receiptDate, Total = receiptTotal, ExcludeTransactionIds = new HashSet() }; // Act var result = await service.FindMatchingTransactionsAsync(criteria); // Assert Assert.Single(result); Assert.Equal(1, result[0].Id); Assert.True(result[0].IsCloseAmount); } [Fact] public async Task FindMatchingTransactionsAsync_MarksExactAmountMatch() { // Arrange using var context = DbContextHelper.CreateInMemoryContext(); var service = new ReceiptMatchingService(context); var account = new Account { Id = 1, Institution = "Test Bank", AccountType = AccountType.Checking, Last4 = "1234", Owner = "Test Owner" }; context.Accounts.Add(account); var receiptDate = DateTime.Now; var receiptTotal = 100.00m; var exactMatch = new Transaction { Id = 1, Date = receiptDate, Amount = -100.00m, // Exact match Name = "Store A", Memo = "Test", AccountId = 1 }; context.Transactions.Add(exactMatch); await context.SaveChangesAsync(); var criteria = new ReceiptMatchCriteria { ReceiptDate = receiptDate, Total = receiptTotal, ExcludeTransactionIds = new HashSet() }; // Act var result = await service.FindMatchingTransactionsAsync(criteria); // Assert Assert.Single(result); Assert.True(result[0].IsExactAmount); Assert.False(result[0].IsCloseAmount); } [Fact] public async Task FindMatchingTransactionsAsync_ExcludesTransactionsWithReceipts() { // Arrange using var context = DbContextHelper.CreateInMemoryContext(); var service = new ReceiptMatchingService(context); var account = new Account { Id = 1, Institution = "Test Bank", AccountType = AccountType.Checking, Last4 = "1234", Owner = "Test Owner" }; context.Accounts.Add(account); var receiptDate = DateTime.Now; var transaction1 = new Transaction { Id = 1, Date = receiptDate, Amount = -50.00m, Name = "Store A", Memo = "Test", AccountId = 1 }; var transaction2 = new Transaction { Id = 2, Date = receiptDate, Amount = -50.00m, Name = "Store B", Memo = "Test", AccountId = 1 }; context.Transactions.AddRange(transaction1, transaction2); await context.SaveChangesAsync(); var criteria = new ReceiptMatchCriteria { ReceiptDate = receiptDate, ExcludeTransactionIds = new HashSet { 1 } // Exclude transaction 1 }; // Act var result = await service.FindMatchingTransactionsAsync(criteria); // Assert Assert.Single(result); Assert.Equal(2, result[0].Id); } [Fact] public async Task FindMatchingTransactionsAsync_UsesDueDateForBills() { // Arrange using var context = DbContextHelper.CreateInMemoryContext(); var service = new ReceiptMatchingService(context); var account = new Account { Id = 1, Institution = "Test Bank", AccountType = AccountType.Checking, Last4 = "1234", Owner = "Test Owner" }; context.Accounts.Add(account); var receiptDate = new DateTime(2025, 1, 1); var dueDate = new DateTime(2025, 1, 15); // Transaction on due date + 3 days (should match for bills) var transaction = new Transaction { Id = 1, Date = dueDate.AddDays(3), Amount = -50.00m, Name = "Utility Company", Memo = "Test", AccountId = 1 }; context.Transactions.Add(transaction); await context.SaveChangesAsync(); var criteria = new ReceiptMatchCriteria { ReceiptDate = receiptDate, DueDate = dueDate, // Bill with due date ExcludeTransactionIds = new HashSet() }; // Act var result = await service.FindMatchingTransactionsAsync(criteria); // Assert Assert.Single(result); Assert.Equal(1, result[0].Id); } }