256 lines
8.9 KiB
C#
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));
|
|
}
|
|
}
|
|
|