diff --git a/MoneyMap.Tests/MoneyMap.Tests.csproj b/MoneyMap.Tests/MoneyMap.Tests.csproj
new file mode 100644
index 0000000..3b7f223
--- /dev/null
+++ b/MoneyMap.Tests/MoneyMap.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net9.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MoneyMap.Tests/Services/AccountServiceTests.cs b/MoneyMap.Tests/Services/AccountServiceTests.cs
new file mode 100644
index 0000000..498ebfa
--- /dev/null
+++ b/MoneyMap.Tests/Services/AccountServiceTests.cs
@@ -0,0 +1,237 @@
+using MoneyMap.Models;
+using MoneyMap.Services;
+using MoneyMap.Tests.TestHelpers;
+using Xunit;
+
+namespace MoneyMap.Tests.Services;
+
+public class AccountServiceTests
+{
+ [Fact]
+ public async Task GetAllAccountsWithStatsAsync_ReturnsAccountsWithTransactionCounts()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new AccountService(context);
+
+ var account1 = new Account
+ {
+ Id = 1,
+ Institution = "Bank A",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "John Doe"
+ };
+ var account2 = new Account
+ {
+ Id = 2,
+ Institution = "Bank B",
+ AccountType = AccountType.Savings,
+ Last4 = "5678",
+ Owner = "Jane Smith"
+ };
+ context.Accounts.AddRange(account1, account2);
+
+ var transaction1 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1
+ };
+ var transaction2 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -25.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1
+ };
+ context.Transactions.AddRange(transaction1, transaction2);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetAllAccountsWithStatsAsync();
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ var account1Stats = result.First(a => a.Id == 1);
+ Assert.Equal(2, account1Stats.TransactionCount);
+ var account2Stats = result.First(a => a.Id == 2);
+ Assert.Equal(0, account2Stats.TransactionCount);
+ }
+
+ [Fact]
+ public async Task CanDeleteAccountAsync_ReturnsFalse_WhenAccountHasTransactions()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new AccountService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var transaction = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1
+ };
+ context.Transactions.Add(transaction);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.CanDeleteAccountAsync(1);
+
+ // Assert
+ Assert.False(result.CanDelete);
+ Assert.Contains("transaction", result.Reason, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public async Task CanDeleteAccountAsync_ReturnsTrue_WhenAccountHasNoTransactions()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new AccountService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.CanDeleteAccountAsync(1);
+
+ // Assert
+ Assert.True(result.CanDelete);
+ }
+
+ [Fact]
+ public async Task DeleteAccountAsync_SuccessfullyDeletesAccount_WhenNoTransactions()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new AccountService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.DeleteAccountAsync(1);
+
+ // Assert
+ Assert.True(result.Success);
+ Assert.Empty(context.Accounts);
+ }
+
+ [Fact]
+ public async Task DeleteAccountAsync_FailsToDelete_WhenAccountHasTransactions()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new AccountService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var transaction = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1
+ };
+ context.Transactions.Add(transaction);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.DeleteAccountAsync(1);
+
+ // Assert
+ Assert.False(result.Success);
+ Assert.Single(context.Accounts);
+ }
+
+ [Fact]
+ public async Task GetAccountDetailsAsync_ReturnsFullDetails_WithCardsAndCounts()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new AccountService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var card = new Card
+ {
+ Id = 1,
+ AccountId = 1,
+ Issuer = "VISA",
+ Last4 = "9999",
+ Owner = "Test Owner"
+ };
+ context.Cards.Add(card);
+
+ var transaction = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ CardId = 1
+ };
+ context.Transactions.Add(transaction);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetAccountDetailsAsync(1);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(1, result.Account.Id);
+ Assert.Single(result.Cards);
+ Assert.Equal(1, result.Cards[0].TransactionCount);
+ Assert.Equal(1, result.TransactionCount);
+ }
+}
diff --git a/MoneyMap.Tests/Services/CardServiceTests.cs b/MoneyMap.Tests/Services/CardServiceTests.cs
new file mode 100644
index 0000000..2e574d1
--- /dev/null
+++ b/MoneyMap.Tests/Services/CardServiceTests.cs
@@ -0,0 +1,232 @@
+using MoneyMap.Models;
+using MoneyMap.Services;
+using MoneyMap.Tests.TestHelpers;
+using Xunit;
+
+namespace MoneyMap.Tests.Services;
+
+public class CardServiceTests
+{
+ [Fact]
+ public async Task GetAllCardsWithStatsAsync_ReturnsCardsWithTransactionCounts()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new CardService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var card1 = new Card
+ {
+ Id = 1,
+ AccountId = 1,
+ Issuer = "VISA",
+ Last4 = "1111",
+ Owner = "John Doe"
+ };
+ var card2 = new Card
+ {
+ Id = 2,
+ AccountId = 1,
+ Issuer = "Mastercard",
+ Last4 = "2222",
+ Owner = "Jane Smith"
+ };
+ context.Cards.AddRange(card1, card2);
+
+ var transaction = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ CardId = 1
+ };
+ context.Transactions.Add(transaction);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetAllCardsWithStatsAsync();
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ var card1Stats = result.First(c => c.Card.Id == 1);
+ Assert.Equal(1, card1Stats.TransactionCount);
+ var card2Stats = result.First(c => c.Card.Id == 2);
+ Assert.Equal(0, card2Stats.TransactionCount);
+ }
+
+ [Fact]
+ public async Task CanDeleteCardAsync_ReturnsFalse_WhenCardHasTransactions()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new CardService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var card = new Card
+ {
+ Id = 1,
+ AccountId = 1,
+ Issuer = "VISA",
+ Last4 = "9999",
+ Owner = "Test Owner"
+ };
+ context.Cards.Add(card);
+
+ var transaction = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ CardId = 1
+ };
+ context.Transactions.Add(transaction);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.CanDeleteCardAsync(1);
+
+ // Assert
+ Assert.False(result.CanDelete);
+ Assert.Contains("transaction", result.Reason, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public async Task CanDeleteCardAsync_ReturnsTrue_WhenCardHasNoTransactions()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new CardService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var card = new Card
+ {
+ Id = 1,
+ AccountId = 1,
+ Issuer = "VISA",
+ Last4 = "9999",
+ Owner = "Test Owner"
+ };
+ context.Cards.Add(card);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.CanDeleteCardAsync(1);
+
+ // Assert
+ Assert.True(result.CanDelete);
+ }
+
+ [Fact]
+ public async Task DeleteCardAsync_SuccessfullyDeletesCard_WhenNoTransactions()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new CardService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var card = new Card
+ {
+ Id = 1,
+ AccountId = 1,
+ Issuer = "VISA",
+ Last4 = "9999",
+ Owner = "Test Owner"
+ };
+ context.Cards.Add(card);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.DeleteCardAsync(1);
+
+ // Assert
+ Assert.True(result.Success);
+ Assert.Empty(context.Cards);
+ }
+
+ [Fact]
+ public async Task DeleteCardAsync_FailsToDelete_WhenCardHasTransactions()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new CardService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var card = new Card
+ {
+ Id = 1,
+ AccountId = 1,
+ Issuer = "VISA",
+ Last4 = "9999",
+ Owner = "Test Owner"
+ };
+ context.Cards.Add(card);
+
+ var transaction = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ CardId = 1
+ };
+ context.Transactions.Add(transaction);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.DeleteCardAsync(1);
+
+ // Assert
+ Assert.False(result.Success);
+ Assert.Single(context.Cards);
+ }
+}
diff --git a/MoneyMap.Tests/Services/MerchantServiceTests.cs b/MoneyMap.Tests/Services/MerchantServiceTests.cs
new file mode 100644
index 0000000..985352d
--- /dev/null
+++ b/MoneyMap.Tests/Services/MerchantServiceTests.cs
@@ -0,0 +1,189 @@
+using MoneyMap.Models;
+using MoneyMap.Services;
+using MoneyMap.Tests.TestHelpers;
+using Xunit;
+
+namespace MoneyMap.Tests.Services;
+
+public class MerchantServiceTests
+{
+ [Fact]
+ public async Task GetOrCreateAsync_CreatesNewMerchant_WhenDoesNotExist()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new MerchantService(context);
+
+ // Act
+ var result = await service.GetOrCreateAsync("Walmart");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("Walmart", result.Name);
+ Assert.Single(context.Merchants);
+ }
+
+ [Fact]
+ public async Task GetOrCreateAsync_ReturnsExistingMerchant_WhenExists()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new MerchantService(context);
+
+ var existingMerchant = new Merchant { Id = 1, Name = "Walmart" };
+ context.Merchants.Add(existingMerchant);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetOrCreateAsync("Walmart");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(1, result.Id);
+ Assert.Single(context.Merchants);
+ }
+
+ [Fact]
+ public async Task GetAllMerchantsWithStatsAsync_ReturnsMerchantsWithStats()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new MerchantService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var merchant1 = new Merchant { Id = 1, Name = "Walmart" };
+ var merchant2 = new Merchant { Id = 2, Name = "Target" };
+ context.Merchants.AddRange(merchant1, merchant2);
+
+ var transaction = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ MerchantId = 1
+ };
+ context.Transactions.Add(transaction);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetAllMerchantsWithStatsAsync();
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ var walmart = result.First(m => m.Name == "Walmart");
+ Assert.Equal(1, walmart.TransactionCount);
+ var target = result.First(m => m.Name == "Target");
+ Assert.Equal(0, target.TransactionCount);
+ }
+
+ [Fact]
+ public async Task UpdateMerchantAsync_SuccessfullyUpdatesName()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new MerchantService(context);
+
+ var merchant = new Merchant { Id = 1, Name = "Walmart" };
+ context.Merchants.Add(merchant);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.UpdateMerchantAsync(1, "Walmart Supercenter");
+
+ // Assert
+ Assert.True(result.Success);
+ var updated = await context.Merchants.FindAsync(1);
+ Assert.Equal("Walmart Supercenter", updated!.Name);
+ }
+
+ [Fact]
+ public async Task UpdateMerchantAsync_FailsWhenDuplicateName()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new MerchantService(context);
+
+ var merchant1 = new Merchant { Id = 1, Name = "Walmart" };
+ var merchant2 = new Merchant { Id = 2, Name = "Target" };
+ context.Merchants.AddRange(merchant1, merchant2);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.UpdateMerchantAsync(1, "Target");
+
+ // Assert
+ Assert.False(result.Success);
+ Assert.Contains("already exists", result.Message);
+ }
+
+ [Fact]
+ public async Task DeleteMerchantAsync_SuccessfullyDeletes()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new MerchantService(context);
+
+ var merchant = new Merchant { Id = 1, Name = "Walmart" };
+ context.Merchants.Add(merchant);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.DeleteMerchantAsync(1);
+
+ // Assert
+ Assert.True(result.Success);
+ Assert.Empty(context.Merchants);
+ }
+
+ [Fact]
+ public async Task DeleteMerchantAsync_ReportsTransactionAndMappingCounts()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new MerchantService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var merchant = new Merchant { Id = 1, Name = "Walmart" };
+ context.Merchants.Add(merchant);
+
+ var transaction = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ MerchantId = 1
+ };
+ context.Transactions.Add(transaction);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.DeleteMerchantAsync(1);
+
+ // Assert
+ Assert.True(result.Success);
+ Assert.Equal(1, result.TransactionCount);
+ Assert.Contains("1 transaction", result.Message);
+ }
+}
diff --git a/MoneyMap.Tests/Services/ReceiptMatchingServiceTests.cs b/MoneyMap.Tests/Services/ReceiptMatchingServiceTests.cs
new file mode 100644
index 0000000..9b74428
--- /dev/null
+++ b/MoneyMap.Tests/Services/ReceiptMatchingServiceTests.cs
@@ -0,0 +1,330 @@
+using MoneyMap.Models;
+using MoneyMap.Services;
+using MoneyMap.Tests.TestHelpers;
+using Xunit;
+
+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);
+ }
+}
diff --git a/MoneyMap.Tests/Services/ReferenceDataServiceTests.cs b/MoneyMap.Tests/Services/ReferenceDataServiceTests.cs
new file mode 100644
index 0000000..22bd9db
--- /dev/null
+++ b/MoneyMap.Tests/Services/ReferenceDataServiceTests.cs
@@ -0,0 +1,221 @@
+using MoneyMap.Models;
+using MoneyMap.Services;
+using MoneyMap.Tests.TestHelpers;
+using Xunit;
+
+namespace MoneyMap.Tests.Services;
+
+public class ReferenceDataServiceTests
+{
+ [Fact]
+ public async Task GetAvailableCategoriesAsync_ReturnsDistinctSortedCategories()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new ReferenceDataService(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
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ Category = "Groceries"
+ };
+ var transaction2 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -30.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ Category = "Gas"
+ };
+ var transaction3 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -20.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ Category = "Groceries" // Duplicate
+ };
+ context.Transactions.AddRange(transaction1, transaction2, transaction3);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetAvailableCategoriesAsync();
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Contains("Groceries", result);
+ Assert.Contains("Gas", result);
+ Assert.Equal("Gas", result[0]); // Alphabetically sorted
+ Assert.Equal("Groceries", result[1]);
+ }
+
+ [Fact]
+ public async Task GetAvailableCategoriesAsync_ExcludesEmptyCategories()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new ReferenceDataService(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
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ Category = "Groceries"
+ };
+ var transaction2 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -30.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ Category = "" // Empty
+ };
+ var transaction3 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -20.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ Category = " " // Whitespace
+ };
+ context.Transactions.AddRange(transaction1, transaction2, transaction3);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetAvailableCategoriesAsync();
+
+ // Assert
+ Assert.Single(result);
+ Assert.Equal("Groceries", result[0]);
+ }
+
+ [Fact]
+ public async Task GetAvailableMerchantsAsync_ReturnsSortedMerchants()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new ReferenceDataService(context);
+
+ var merchant1 = new Merchant { Name = "Walmart" };
+ var merchant2 = new Merchant { Name = "Amazon" };
+ var merchant3 = new Merchant { Name = "Target" };
+ context.Merchants.AddRange(merchant1, merchant2, merchant3);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetAvailableMerchantsAsync();
+
+ // Assert
+ Assert.Equal(3, result.Count);
+ Assert.Equal("Amazon", result[0].Name); // Alphabetically sorted
+ Assert.Equal("Target", result[1].Name);
+ Assert.Equal("Walmart", result[2].Name);
+ }
+
+ [Fact]
+ public async Task GetAvailableCardsAsync_ReturnsSortedCards()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new ReferenceDataService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var card1 = new Card
+ {
+ AccountId = 1,
+ Issuer = "VISA",
+ Last4 = "3333",
+ Owner = "Bob"
+ };
+ var card2 = new Card
+ {
+ AccountId = 1,
+ Issuer = "Mastercard",
+ Last4 = "1111",
+ Owner = "Alice"
+ };
+ context.Cards.AddRange(card1, card2);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetAvailableCardsAsync();
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("Alice", result[0].Owner); // Sorted by owner
+ Assert.Equal("Bob", result[1].Owner);
+ }
+
+ [Fact]
+ public async Task GetAvailableAccountsAsync_ReturnsSortedAccounts()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new ReferenceDataService(context);
+
+ var account1 = new Account
+ {
+ Institution = "Wells Fargo",
+ AccountType = AccountType.Checking,
+ Last4 = "5678",
+ Owner = "Test"
+ };
+ var account2 = new Account
+ {
+ Institution = "Bank of America",
+ AccountType = AccountType.Savings,
+ Last4 = "1234",
+ Owner = "Test"
+ };
+ context.Accounts.AddRange(account1, account2);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetAvailableAccountsAsync();
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ Assert.Equal("Bank of America", result[0].Institution); // Sorted by institution
+ Assert.Equal("Wells Fargo", result[1].Institution);
+ }
+}
diff --git a/MoneyMap.Tests/Services/TransactionServiceTests.cs b/MoneyMap.Tests/Services/TransactionServiceTests.cs
new file mode 100644
index 0000000..1ad8e2e
--- /dev/null
+++ b/MoneyMap.Tests/Services/TransactionServiceTests.cs
@@ -0,0 +1,229 @@
+using MoneyMap.Models;
+using MoneyMap.Services;
+using MoneyMap.Tests.TestHelpers;
+using Xunit;
+
+namespace MoneyMap.Tests.Services;
+
+public class TransactionServiceTests
+{
+ [Fact]
+ public async Task IsDuplicateAsync_ReturnsFalse_WhenTransactionDoesNotExist()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new TransactionService(context);
+
+ var transaction = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test Store",
+ Memo = "Test purchase",
+ AccountId = 1,
+ CardId = 1
+ };
+
+ // Act
+ var result = await service.IsDuplicateAsync(transaction);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public async Task IsDuplicateAsync_ReturnsTrue_WhenExactDuplicateExists()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new TransactionService(context);
+
+ // Add an account first (required for transaction)
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var existingTransaction = new Transaction
+ {
+ Date = new DateTime(2025, 1, 15),
+ Amount = -50.00m,
+ Name = "Test Store",
+ Memo = "Test purchase",
+ AccountId = 1,
+ CardId = 1
+ };
+ context.Transactions.Add(existingTransaction);
+ await context.SaveChangesAsync();
+
+ var duplicateTransaction = new Transaction
+ {
+ Date = new DateTime(2025, 1, 15),
+ Amount = -50.00m,
+ Name = "Test Store",
+ Memo = "Test purchase",
+ AccountId = 1,
+ CardId = 1
+ };
+
+ // Act
+ var result = await service.IsDuplicateAsync(duplicateTransaction);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public async Task IsDuplicateAsync_ReturnsFalse_WhenAmountDiffers()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new TransactionService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var existingTransaction = new Transaction
+ {
+ Date = new DateTime(2025, 1, 15),
+ Amount = -50.00m,
+ Name = "Test Store",
+ Memo = "Test purchase",
+ AccountId = 1,
+ CardId = 1
+ };
+ context.Transactions.Add(existingTransaction);
+ await context.SaveChangesAsync();
+
+ var differentTransaction = new Transaction
+ {
+ Date = new DateTime(2025, 1, 15),
+ Amount = -51.00m, // Different amount
+ Name = "Test Store",
+ Memo = "Test purchase",
+ AccountId = 1,
+ CardId = 1
+ };
+
+ // Act
+ var result = await service.IsDuplicateAsync(differentTransaction);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public async Task GetTransactionByIdAsync_ReturnsNull_WhenTransactionDoesNotExist()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new TransactionService(context);
+
+ // Act
+ var result = await service.GetTransactionByIdAsync(999);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public async Task GetTransactionByIdAsync_ReturnsTransaction_WhenExists()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new TransactionService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var transaction = new Transaction
+ {
+ Id = 1,
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test Store",
+ Memo = "Test purchase",
+ AccountId = 1
+ };
+ context.Transactions.Add(transaction);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetTransactionByIdAsync(1);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("Test Store", result.Name);
+ Assert.Equal(-50.00m, result.Amount);
+ }
+
+ [Fact]
+ public async Task DeleteTransactionAsync_ReturnsTrue_WhenTransactionExists()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new TransactionService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var transaction = new Transaction
+ {
+ Id = 1,
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test Store",
+ Memo = "Test purchase",
+ AccountId = 1
+ };
+ context.Transactions.Add(transaction);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.DeleteTransactionAsync(1);
+
+ // Assert
+ Assert.True(result);
+ Assert.Empty(context.Transactions);
+ }
+
+ [Fact]
+ public async Task DeleteTransactionAsync_ReturnsFalse_WhenTransactionDoesNotExist()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new TransactionService(context);
+
+ // Act
+ var result = await service.DeleteTransactionAsync(999);
+
+ // Assert
+ Assert.False(result);
+ }
+}
diff --git a/MoneyMap.Tests/Services/TransactionStatisticsServiceTests.cs b/MoneyMap.Tests/Services/TransactionStatisticsServiceTests.cs
new file mode 100644
index 0000000..68d13db
--- /dev/null
+++ b/MoneyMap.Tests/Services/TransactionStatisticsServiceTests.cs
@@ -0,0 +1,188 @@
+using MoneyMap.Models;
+using MoneyMap.Services;
+using MoneyMap.Tests.TestHelpers;
+using Xunit;
+
+namespace MoneyMap.Tests.Services;
+
+public class TransactionStatisticsServiceTests
+{
+ [Fact]
+ public async Task CalculateStatsAsync_ReturnsCorrectStatistics()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new TransactionStatisticsService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var debit1 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Store A",
+ Memo = "Purchase",
+ AccountId = 1
+ };
+ var debit2 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -30.00m,
+ Name = "Store B",
+ Memo = "Purchase",
+ AccountId = 1
+ };
+ var credit = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = 100.00m,
+ Name = "Deposit",
+ Memo = "Paycheck",
+ AccountId = 1
+ };
+ context.Transactions.AddRange(debit1, debit2, credit);
+ await context.SaveChangesAsync();
+
+ var query = context.Transactions.AsQueryable();
+
+ // Act
+ var result = await service.CalculateStatsAsync(query);
+
+ // Assert
+ Assert.Equal(3, result.Count);
+ Assert.Equal(-80.00m, result.TotalDebits);
+ Assert.Equal(100.00m, result.TotalCredits);
+ Assert.Equal(20.00m, result.NetAmount);
+ }
+
+ [Fact]
+ public async Task GetCategorizationStatsAsync_ReturnsCorrectCounts()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new TransactionStatisticsService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var categorized1 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ Category = "Groceries"
+ };
+ var categorized2 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -30.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ Category = "Gas"
+ };
+ var uncategorized = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -20.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ Category = ""
+ };
+ context.Transactions.AddRange(categorized1, categorized2, uncategorized);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetCategorizationStatsAsync();
+
+ // Assert
+ Assert.Equal(3, result.TotalTransactions);
+ Assert.Equal(2, result.Categorized);
+ Assert.Equal(1, result.Uncategorized);
+ }
+
+ [Fact]
+ public async Task GetCardStatsForAccountAsync_ReturnsStatsForLinkedCards()
+ {
+ // Arrange
+ using var context = DbContextHelper.CreateInMemoryContext();
+ var service = new TransactionStatisticsService(context);
+
+ var account = new Account
+ {
+ Id = 1,
+ Institution = "Test Bank",
+ AccountType = AccountType.Checking,
+ Last4 = "1234",
+ Owner = "Test Owner"
+ };
+ context.Accounts.Add(account);
+
+ var card1 = new Card
+ {
+ Id = 1,
+ AccountId = 1,
+ Issuer = "VISA",
+ Last4 = "1111",
+ Owner = "Test"
+ };
+ var card2 = new Card
+ {
+ Id = 2,
+ AccountId = 1,
+ Issuer = "Mastercard",
+ Last4 = "2222",
+ Owner = "Test"
+ };
+ context.Cards.AddRange(card1, card2);
+
+ var transaction1 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -50.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ CardId = 1
+ };
+ var transaction2 = new Transaction
+ {
+ Date = DateTime.Now,
+ Amount = -30.00m,
+ Name = "Test",
+ Memo = "Test",
+ AccountId = 1,
+ CardId = 1
+ };
+ context.Transactions.AddRange(transaction1, transaction2);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await service.GetCardStatsForAccountAsync(1);
+
+ // Assert
+ Assert.Equal(2, result.Count);
+ var card1Stats = result.First(c => c.Card.Id == 1);
+ Assert.Equal(2, card1Stats.TransactionCount);
+ var card2Stats = result.First(c => c.Card.Id == 2);
+ Assert.Equal(0, card2Stats.TransactionCount);
+ }
+}
diff --git a/MoneyMap.Tests/TestHelpers/DbContextHelper.cs b/MoneyMap.Tests/TestHelpers/DbContextHelper.cs
new file mode 100644
index 0000000..780d34f
--- /dev/null
+++ b/MoneyMap.Tests/TestHelpers/DbContextHelper.cs
@@ -0,0 +1,20 @@
+using Microsoft.EntityFrameworkCore;
+using MoneyMap.Data;
+
+namespace MoneyMap.Tests.TestHelpers;
+
+public static class DbContextHelper
+{
+ ///
+ /// Creates an in-memory database context for testing.
+ /// Each call creates a unique database to ensure test isolation.
+ ///
+ public static MoneyMapContext CreateInMemoryContext()
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
+ .Options;
+
+ return new MoneyMapContext(options);
+ }
+}
diff --git a/MoneyMap.sln b/MoneyMap.sln
index 8576927..a914c76 100644
--- a/MoneyMap.sln
+++ b/MoneyMap.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.14.36429.23
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoneyMap", "MoneyMap\MoneyMap.csproj", "{B273A467-3592-4675-B1EC-C41C9CE455DB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoneyMap.Tests", "MoneyMap.Tests\MoneyMap.Tests.csproj", "{4CAD4283-4E2D-B998-4839-03B72BDDBEF5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
{B273A467-3592-4675-B1EC-C41C9CE455DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B273A467-3592-4675-B1EC-C41C9CE455DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B273A467-3592-4675-B1EC-C41C9CE455DB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4CAD4283-4E2D-B998-4839-03B72BDDBEF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4CAD4283-4E2D-B998-4839-03B72BDDBEF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4CAD4283-4E2D-B998-4839-03B72BDDBEF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4CAD4283-4E2D-B998-4839-03B72BDDBEF5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE