budget/BudgetApp.Tests/Application/LedgerServiceTests.cs
2026-01-03 10:29:03 -06:00

256 lines
8.9 KiB
C#

namespace BudgetApp.Tests.Application;
using BudgetApp.Application.Services;
using BudgetApp.Domain.Interfaces;
using BudgetApp.Domain.Models;
using Moq;
using Xunit;
public class LedgerServiceTests
{
private readonly Mock<ILedgerRepository> _mockLedgerRepository;
private readonly Mock<IBudgetRepository> _mockBudgetRepository;
private readonly LedgerService _service;
public LedgerServiceTests()
{
_mockLedgerRepository = new Mock<ILedgerRepository>();
_mockBudgetRepository = new Mock<IBudgetRepository>();
_service = new LedgerService(_mockLedgerRepository.Object, _mockBudgetRepository.Object);
}
[Fact]
public async Task RecordExpenseAsync_WithValidParameters_CreatesAndSavesEntry()
{
// Arrange
var budget = new BaseBudget("Grocery", new Money(100m, "USD"));
var budgetId = budget.Id;
var budgetAllocations = new Dictionary<Guid, Money>
{
{ budgetId, new Money(100m, "USD") }
};
_mockBudgetRepository.Setup(r => r.GetAllAsync())
.ReturnsAsync(new List<Budget> { budget });
_mockBudgetRepository.Setup(r => r.GetByIdAsync(budgetId))
.ReturnsAsync(budget);
_mockBudgetRepository.Setup(r => r.SaveAsync(It.IsAny<Budget>()))
.Returns(Task.CompletedTask);
_mockLedgerRepository.Setup(r => r.GetOrCreateAsync())
.ReturnsAsync(new Ledger());
_mockLedgerRepository.Setup(r => r.SaveAsync(It.IsAny<Ledger>()))
.Returns(Task.CompletedTask);
_mockLedgerRepository.Setup(r => r.SaveEntryAsync(It.IsAny<LedgerEntry>()))
.Returns(Task.CompletedTask);
// Act
var entry = await _service.RecordExpenseAsync(
DateTime.Now,
"Grocery Shopping",
new Money(100m, "USD"),
budgetAllocations);
// Assert
Assert.NotNull(entry);
Assert.Equal(EntryType.Expense, entry.Type);
Assert.Equal(100m, entry.Amount.Amount);
_mockLedgerRepository.Verify(r => r.SaveEntryAsync(It.IsAny<LedgerEntry>()), Times.Once);
_mockBudgetRepository.Verify(r => r.SaveAsync(It.IsAny<Budget>()), Times.Once);
}
[Fact]
public async Task RecordExpenseAsync_WithAllocationsNotMatchingAmount_ThrowsException()
{
// Arrange
var budgetId = Guid.NewGuid();
var budget = new BaseBudget("Grocery", new Money(100m, "USD"));
var budgetAllocations = new Dictionary<Guid, Money>
{
{ budgetId, new Money(50m, "USD") }
};
_mockBudgetRepository.Setup(r => r.GetAllAsync())
.ReturnsAsync(new List<Budget> { budget });
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_service.RecordExpenseAsync(
DateTime.Now,
"Grocery Shopping",
new Money(100m, "USD"),
budgetAllocations));
}
[Fact]
public async Task RecordExpenseAsync_WithNonExistentBudget_ThrowsException()
{
// Arrange
var budgetId = Guid.NewGuid();
var budgetAllocations = new Dictionary<Guid, Money>
{
{ budgetId, new Money(100m, "USD") }
};
_mockBudgetRepository.Setup(r => r.GetAllAsync())
.ReturnsAsync(new List<Budget>());
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_service.RecordExpenseAsync(
DateTime.Now,
"Grocery Shopping",
new Money(100m, "USD"),
budgetAllocations));
}
[Fact]
public async Task RecordExpenseAsync_WithMultipleBudgets_UpdatesAllBudgets()
{
// Arrange
var budget1 = new BaseBudget("Grocery", new Money(100m, "USD"));
var budget2 = new BaseBudget("Medical", new Money(50m, "USD"));
var budgetId1 = budget1.Id;
var budgetId2 = budget2.Id;
var budgetAllocations = new Dictionary<Guid, Money>
{
{ budgetId1, new Money(90m, "USD") },
{ budgetId2, new Money(10m, "USD") }
};
_mockBudgetRepository.Setup(r => r.GetAllAsync())
.ReturnsAsync(new List<Budget> { budget1, budget2 });
_mockBudgetRepository.Setup(r => r.GetByIdAsync(budgetId1))
.ReturnsAsync(budget1);
_mockBudgetRepository.Setup(r => r.GetByIdAsync(budgetId2))
.ReturnsAsync(budget2);
_mockBudgetRepository.Setup(r => r.SaveAsync(It.IsAny<Budget>()))
.Returns(Task.CompletedTask);
_mockLedgerRepository.Setup(r => r.GetOrCreateAsync())
.ReturnsAsync(new Ledger());
_mockLedgerRepository.Setup(r => r.SaveAsync(It.IsAny<Ledger>()))
.Returns(Task.CompletedTask);
_mockLedgerRepository.Setup(r => r.SaveEntryAsync(It.IsAny<LedgerEntry>()))
.Returns(Task.CompletedTask);
// Act
var entry = await _service.RecordExpenseAsync(
DateTime.Now,
"Grocery Shopping",
new Money(100m, "USD"),
budgetAllocations);
// Assert
Assert.NotNull(entry);
Assert.Equal(2, entry.BudgetAllocations.Count);
_mockBudgetRepository.Verify(r => r.SaveAsync(It.IsAny<Budget>()), Times.Exactly(2));
}
[Fact]
public async Task RecordIncomeAsync_WithValidParameters_CreatesAndSavesEntry()
{
// Arrange
var budget = new BaseBudget("Grocery", new Money(100m, "USD"));
var budgetId = budget.Id;
var budgetAllocations = new Dictionary<Guid, Money>
{
{ budgetId, new Money(1000m, "USD") }
};
_mockBudgetRepository.Setup(r => r.GetAllAsync())
.ReturnsAsync(new List<Budget> { budget });
_mockBudgetRepository.Setup(r => r.GetByIdAsync(budgetId))
.ReturnsAsync(budget);
_mockBudgetRepository.Setup(r => r.SaveAsync(It.IsAny<Budget>()))
.Returns(Task.CompletedTask);
_mockLedgerRepository.Setup(r => r.GetOrCreateAsync())
.ReturnsAsync(new Ledger());
_mockLedgerRepository.Setup(r => r.SaveAsync(It.IsAny<Ledger>()))
.Returns(Task.CompletedTask);
_mockLedgerRepository.Setup(r => r.SaveEntryAsync(It.IsAny<LedgerEntry>()))
.Returns(Task.CompletedTask);
// Act
var entry = await _service.RecordIncomeAsync(
DateTime.Now,
"Salary",
new Money(1000m, "USD"),
budgetAllocations);
// Assert
Assert.NotNull(entry);
Assert.Equal(EntryType.Income, entry.Type);
Assert.Equal(1000m, entry.Amount.Amount);
_mockLedgerRepository.Verify(r => r.SaveEntryAsync(It.IsAny<LedgerEntry>()), Times.Once);
_mockBudgetRepository.Verify(r => r.SaveAsync(It.IsAny<Budget>()), Times.Once);
}
[Fact]
public async Task RecordIncomeAsync_WithAllocationsNotMatchingAmount_ThrowsException()
{
// Arrange
var budgetId = Guid.NewGuid();
var budget = new BaseBudget("Grocery", new Money(100m, "USD"));
var budgetAllocations = new Dictionary<Guid, Money>
{
{ budgetId, new Money(500m, "USD") }
};
_mockBudgetRepository.Setup(r => r.GetAllAsync())
.ReturnsAsync(new List<Budget> { budget });
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_service.RecordIncomeAsync(
DateTime.Now,
"Salary",
new Money(1000m, "USD"),
budgetAllocations));
}
[Fact]
public async Task GetTotalBudgetBalanceAsync_ReturnsSumOfAllBudgets()
{
// Arrange
var budgets = new List<Budget>
{
new BaseBudget("Grocery", new Money(100m, "USD")),
new BaseBudget("Medical", new Money(200m, "USD"))
};
_mockBudgetRepository.Setup(r => r.GetAllAsync())
.ReturnsAsync(budgets);
// Act
var total = await _service.GetTotalBudgetBalanceAsync("USD");
// Assert
Assert.Equal(300m, total.Amount);
}
[Fact]
public async Task RecordExpenseAsync_WithInsufficientBudgetBalance_ThrowsException()
{
// Arrange
var budget = new BaseBudget("Grocery", new Money(50m, "USD"));
var budgetId = budget.Id;
var budgetAllocations = new Dictionary<Guid, Money>
{
{ budgetId, new Money(100m, "USD") }
};
_mockBudgetRepository.Setup(r => r.GetAllAsync())
.ReturnsAsync(new List<Budget> { budget });
_mockBudgetRepository.Setup(r => r.GetByIdAsync(budgetId))
.ReturnsAsync(budget);
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_service.RecordExpenseAsync(
DateTime.Now,
"Grocery Shopping",
new Money(100m, "USD"),
budgetAllocations));
}
}