KoogleApp/test/Koogle.Tests/Unit/Services/ClubServiceTests.cs

408 lines
12 KiB
C#

using AutoMapper;
using Koogle.Application.DTOs;
using Koogle.Application.Interfaces;
using Koogle.Application.Services;
using Koogle.Domain.Entities;
using Koogle.Domain.Enums;
using Koogle.Domain.Interfaces;
using Koogle.Tests.Common;
using KoogleApp.Data;
using Microsoft.EntityFrameworkCore;
namespace Koogle.Tests.Unit.Services;
/// <summary>
/// Unit tests for ClubService.
/// </summary>
public class ClubServiceTests : IDisposable
{
private readonly Mock<IClubRepository> _repositoryMock;
private readonly Mock<IMapper> _mapperMock;
private readonly Mock<IClubGifService> _gifServiceMock;
private readonly Mock<IBookingCategoryService> _bookingCategoryServiceMock;
private readonly AppDbContext _appDbContext;
private readonly ClubService _sut;
public ClubServiceTests()
{
_repositoryMock = new Mock<IClubRepository>();
_mapperMock = TestMockHelpers.CreateClubMapperMock();
_gifServiceMock = new Mock<IClubGifService>();
_bookingCategoryServiceMock = new Mock<IBookingCategoryService>();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase($"UnitTest_{Guid.NewGuid()}")
.Options;
_appDbContext = new AppDbContext(options);
_sut = new ClubService(
_repositoryMock.Object,
_mapperMock.Object,
_appDbContext,
_gifServiceMock.Object,
_bookingCategoryServiceMock.Object);
}
public void Dispose()
{
_appDbContext.Dispose();
}
#region GetAllAsync Tests
[Fact]
public async Task GetAllAsync_ReturnsEmptyList_WhenNoClubsExist()
{
// Arrange
_repositoryMock.SetupGetAllAsync([]);
// Act
var result = await _sut.GetAllAsync();
// Assert
result.Should().BeEmpty();
_repositoryMock.Verify(r => r.GetAllAsync(It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task GetAllAsync_ReturnsAllClubs_WhenClubsExist()
{
// Arrange
var clubs = TestDataGenerator.CreateClubs(3);
_repositoryMock.SetupGetAllAsync(clubs);
// Act
var result = await _sut.GetAllAsync();
// Assert
result.Should().HaveCount(3);
result.Select(c => c.Id).Should().BeEquivalentTo(clubs.Select(c => c.Id));
}
[Fact]
public async Task GetAllAsync_MapsClubsToDtos_Correctly()
{
// Arrange
var club = TestDataGenerator.CreateClub(
name: "Test Kegelclub",
expenseCalculation: ExpenseCalculation.Average);
_repositoryMock.SetupGetAllAsync([club]);
// Act
var result = await _sut.GetAllAsync();
// Assert
result.Should().ContainSingle();
var dto = result.First();
dto.Name.Should().Be("Test Kegelclub");
dto.ExpenseCalculation.Should().Be(ExpenseCalculation.Average);
}
#endregion
#region GetByIdAsync Tests
[Fact]
public async Task GetByIdAsync_ReturnsNull_WhenClubNotFound()
{
// Arrange
var id = Guid.NewGuid();
_repositoryMock.SetupGetByIdAsync(id, null);
// Act
var result = await _sut.GetByIdAsync(id);
// Assert
result.Should().BeNull();
}
[Fact]
public async Task GetByIdAsync_ReturnsClub_WhenExists()
{
// Arrange
var club = TestDataGenerator.CreateClub();
_repositoryMock.SetupGetByIdAsync(club.Id, club);
// Act
var result = await _sut.GetByIdAsync(club.Id);
// Assert
result.Should().NotBeNull();
result!.Id.Should().Be(club.Id);
result.Name.Should().Be(club.Name);
}
[Theory]
[InlineData("00000000-0000-0000-0000-000000000001")]
[InlineData("11111111-1111-1111-1111-111111111111")]
public async Task GetByIdAsync_QueriesCorrectId(string guidString)
{
// Arrange
var id = Guid.Parse(guidString);
_repositoryMock.SetupGetByIdAsync(null);
// Act
await _sut.GetByIdAsync(id);
// Assert
_repositoryMock.Verify(r => r.GetByIdAsync(id, It.IsAny<CancellationToken>()), Times.Once);
}
#endregion
#region CreateAsync Tests
[Fact]
public async Task CreateAsync_CreatesClubWithCorrectData()
{
// Arrange
var dto = TestDataGenerator.CreateCreateClubDto(
name: "New Club",
expenseCalculation: ExpenseCalculation.Maximum);
_repositoryMock.SetupAddAsync();
// Act
var result = await _sut.CreateAsync(dto);
// Assert
result.Should().NotBeNull();
result.Name.Should().Be("New Club");
result.ExpenseCalculation.Should().Be(ExpenseCalculation.Maximum);
}
[Fact]
public async Task CreateAsync_GeneratesNewId()
{
// Arrange
var dto = TestDataGenerator.CreateCreateClubDto();
Club? capturedClub = null;
_repositoryMock.Setup(r => r.AddAsync(It.IsAny<Club>(), It.IsAny<CancellationToken>()))
.Callback<Club, CancellationToken>((c, _) => capturedClub = c)
.ReturnsAsync((Club c, CancellationToken _) => c);
// Act
await _sut.CreateAsync(dto);
// Assert
capturedClub.Should().NotBeNull();
capturedClub!.Id.Should().NotBe(Guid.Empty);
}
[Fact]
public async Task CreateAsync_SetsCreatedAtToUtcNow()
{
// Arrange
var dto = TestDataGenerator.CreateCreateClubDto();
Club? capturedClub = null;
var beforeCreate = DateTime.UtcNow;
_repositoryMock.Setup(r => r.AddAsync(It.IsAny<Club>(), It.IsAny<CancellationToken>()))
.Callback<Club, CancellationToken>((c, _) => capturedClub = c)
.ReturnsAsync((Club c, CancellationToken _) => c);
// Act
await _sut.CreateAsync(dto);
var afterCreate = DateTime.UtcNow;
// Assert
capturedClub!.CreatedAt.Should().BeOnOrAfter(beforeCreate);
capturedClub.CreatedAt.Should().BeOnOrBefore(afterCreate);
}
[Fact]
public async Task CreateAsync_SetsIsDeletedToFalse()
{
// Arrange
var dto = TestDataGenerator.CreateCreateClubDto();
Club? capturedClub = null;
_repositoryMock.Setup(r => r.AddAsync(It.IsAny<Club>(), It.IsAny<CancellationToken>()))
.Callback<Club, CancellationToken>((c, _) => capturedClub = c)
.ReturnsAsync((Club c, CancellationToken _) => c);
// Act
await _sut.CreateAsync(dto);
// Assert
capturedClub!.IsDeleted.Should().BeFalse();
}
[Theory]
[InlineData(ExpenseCalculation.None)]
[InlineData(ExpenseCalculation.Average)]
[InlineData(ExpenseCalculation.Maximum)]
public async Task CreateAsync_HandlesAllExpenseCalculationTypes(ExpenseCalculation calculation)
{
// Arrange
var dto = TestDataGenerator.CreateCreateClubDto(expenseCalculation: calculation);
_repositoryMock.SetupAddAsync();
// Act
var result = await _sut.CreateAsync(dto);
// Assert
result.ExpenseCalculation.Should().Be(calculation);
}
#endregion
#region UpdateAsync Tests
[Fact]
public async Task UpdateAsync_ThrowsException_WhenClubNotFound()
{
// Arrange
var dto = TestDataGenerator.CreateUpdateClubDto();
_repositoryMock.SetupGetByIdAsync(dto.Id, null);
// Act
var act = () => _sut.UpdateAsync(dto);
// Assert
await act.Should().ThrowAsync<InvalidOperationException>()
.WithMessage($"Club with id {dto.Id} not found.");
}
[Fact]
public async Task UpdateAsync_UpdatesClubProperties()
{
// Arrange
var existingClub = TestDataGenerator.CreateClub(
name: "Old Name",
expenseCalculation: ExpenseCalculation.None);
var dto = TestDataGenerator.CreateUpdateClubDto(
id: existingClub.Id,
name: "New Name",
expenseCalculation: ExpenseCalculation.Maximum);
_repositoryMock.SetupGetByIdAsync(existingClub.Id, existingClub);
_repositoryMock.SetupUpdateAsync();
// Act
var result = await _sut.UpdateAsync(dto);
// Assert
result.Name.Should().Be("New Name");
result.ExpenseCalculation.Should().Be(ExpenseCalculation.Maximum);
}
[Fact]
public async Task UpdateAsync_SetsModifiedAtToUtcNow()
{
// Arrange
var existingClub = TestDataGenerator.CreateClub();
var dto = TestDataGenerator.CreateUpdateClubDto(id: existingClub.Id);
var beforeUpdate = DateTime.UtcNow;
_repositoryMock.SetupGetByIdAsync(existingClub.Id, existingClub);
Club? capturedClub = null;
_repositoryMock.Setup(r => r.UpdateAsync(It.IsAny<Club>(), It.IsAny<CancellationToken>()))
.Callback<Club, CancellationToken>((c, _) => capturedClub = c)
.ReturnsAsync((Club c, CancellationToken _) => c);
// Act
await _sut.UpdateAsync(dto);
var afterUpdate = DateTime.UtcNow;
// Assert
capturedClub!.ModifiedAt.Should().BeOnOrAfter(beforeUpdate);
capturedClub.ModifiedAt.Should().BeOnOrBefore(afterUpdate);
}
[Fact]
public async Task UpdateAsync_CallsRepositoryUpdateAsync()
{
// Arrange
var existingClub = TestDataGenerator.CreateClub();
var dto = TestDataGenerator.CreateUpdateClubDto(id: existingClub.Id);
_repositoryMock.SetupGetByIdAsync(existingClub.Id, existingClub);
_repositoryMock.SetupUpdateAsync();
// Act
await _sut.UpdateAsync(dto);
// Assert
_repositoryMock.Verify(r => r.UpdateAsync(existingClub, It.IsAny<CancellationToken>()), Times.Once);
}
#endregion
#region DeleteAsync Tests
[Fact]
public async Task DeleteAsync_ReturnsTrue_WhenSuccessful()
{
// Arrange
var id = Guid.NewGuid();
_repositoryMock.SetupDeleteAsync(true);
// Act
var result = await _sut.DeleteAsync(id);
// Assert
result.Should().BeTrue();
}
[Fact]
public async Task DeleteAsync_ReturnsFalse_WhenClubNotFound()
{
// Arrange
var id = Guid.NewGuid();
_repositoryMock.SetupDeleteAsync(false);
// Act
var result = await _sut.DeleteAsync(id);
// Assert
result.Should().BeFalse();
}
[Fact]
public async Task DeleteAsync_CallsRepositoryWithCorrectId()
{
// Arrange
var id = Guid.NewGuid();
_repositoryMock.SetupDeleteAsync();
// Act
await _sut.DeleteAsync(id);
// Assert
_repositoryMock.Verify(r => r.DeleteAsync(id, It.IsAny<CancellationToken>()), Times.Once);
}
#endregion
#region Cancellation Token Tests
[Fact]
public async Task GetAllAsync_PassesCancellationToken()
{
// Arrange
var cts = new CancellationTokenSource();
_repositoryMock.SetupGetAllAsync([]);
// Act
await _sut.GetAllAsync(cts.Token);
// Assert
_repositoryMock.Verify(r => r.GetAllAsync(cts.Token), Times.Once);
}
[Fact]
public async Task CreateAsync_PassesCancellationToken()
{
// Arrange
var cts = new CancellationTokenSource();
var dto = TestDataGenerator.CreateCreateClubDto();
_repositoryMock.SetupAddAsync();
// Act
await _sut.CreateAsync(dto, cts.Token);
// Assert
_repositoryMock.Verify(r => r.AddAsync(It.IsAny<Club>(), cts.Token), Times.Once);
}
#endregion
}