using AutoMapper;
using Koogle.Application.DTOs;
using Koogle.Application.Interfaces;
using Koogle.Application.Services;
using Koogle.Domain.Entities;
using Koogle.Domain.Interfaces;
using Koogle.Tests.Common;
using KoogleApp.Data;
using Microsoft.EntityFrameworkCore;
using System.Diagnostics;
namespace Koogle.Tests.Performance;
///
/// Performance tests for critical service operations.
///
public class ServicePerformanceTests
{
private const int SmallDataset = 100;
private const int MediumDataset = 1000;
private const int LargeDataset = 10000;
#region ClubService Performance Tests
[Theory]
[InlineData(SmallDataset, 50)] // 100 records should complete in 50ms
[InlineData(MediumDataset, 200)] // 1000 records should complete in 200ms
public async Task GetAllAsync_CompletesWithinTimeLimit(int recordCount, int maxMilliseconds)
{
// Arrange
var options = new DbContextOptionsBuilder()
.UseInMemoryDatabase($"PerfTest_{Guid.NewGuid()}")
.Options;
await using var context = new AppDbContext(options);
// Seed data
var clubs = Enumerable.Range(0, recordCount)
.Select(_ => TestDataGenerator.CreateClub())
.ToList();
await context.Clubs.AddRangeAsync(clubs);
await context.SaveChangesAsync();
var mapper = TestMockHelpers.CreateClubMapperMock().Object;
var repositoryMock = new Mock();
repositoryMock.Setup(r => r.GetAllAsync(It.IsAny()))
.ReturnsAsync(() => context.Clubs.Where(c => !c.IsDeleted).ToList());
var gifServiceMock = new Mock();
var bookingCategoryServiceMock = new Mock();
var service = new ClubService(repositoryMock.Object, mapper, context, gifServiceMock.Object, bookingCategoryServiceMock.Object);
// Act
var stopwatch = Stopwatch.StartNew();
var result = await service.GetAllAsync();
stopwatch.Stop();
// Assert
result.Should().HaveCount(recordCount);
stopwatch.ElapsedMilliseconds.Should().BeLessThan(maxMilliseconds,
$"GetAllAsync with {recordCount} records should complete within {maxMilliseconds}ms, took {stopwatch.ElapsedMilliseconds}ms");
}
[Fact]
public async Task CreateAsync_CompletesQuickly()
{
// Arrange
var options = new DbContextOptionsBuilder()
.UseInMemoryDatabase($"PerfTest_CreateAsync_{Guid.NewGuid()}")
.Options;
await using var context = new AppDbContext(options);
var repositoryMock = new Mock();
repositoryMock.Setup(r => r.AddAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync((Club c, CancellationToken _) => c);
var mapperMock = TestMockHelpers.CreateClubMapperMock();
var gifServiceMock = new Mock();
var bookingCategoryServiceMock = new Mock();
var service = new ClubService(repositoryMock.Object, mapperMock.Object, context, gifServiceMock.Object, bookingCategoryServiceMock.Object);
var dto = TestDataGenerator.CreateCreateClubDto();
// Act
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
await service.CreateAsync(dto);
}
stopwatch.Stop();
// Assert - 100 creates should complete in under 100ms (1ms per create)
stopwatch.ElapsedMilliseconds.Should().BeLessThan(100,
$"100 CreateAsync calls should complete within 100ms, took {stopwatch.ElapsedMilliseconds}ms");
}
#endregion
#region AutoMapper Performance Tests
[Theory]
[InlineData(SmallDataset, 50)]
[InlineData(MediumDataset, 200)]
[InlineData(LargeDataset, 1000)]
public void Mapper_MapsClubsToDtos_Efficiently(int count, int maxMilliseconds)
{
// Arrange
var mapper = TestMockHelpers.CreateClubMapperMock().Object;
var clubs = Enumerable.Range(0, count)
.Select(_ => TestDataGenerator.CreateClub())
.ToList();
// Act
var stopwatch = Stopwatch.StartNew();
var result = mapper.Map>(clubs);
stopwatch.Stop();
// Assert
result.Should().HaveCount(count);
stopwatch.ElapsedMilliseconds.Should().BeLessThan(maxMilliseconds,
$"Mapping {count} clubs should complete within {maxMilliseconds}ms, took {stopwatch.ElapsedMilliseconds}ms");
}
#endregion
#region Fluxor Reducer Performance Tests
[Theory]
[InlineData(SmallDataset)]
[InlineData(MediumDataset)]
public void Reducers_HandleLargeStateLists_Efficiently(int clubCount)
{
// Arrange
var clubs = TestDataGenerator.CreateClubDtos(clubCount);
var state = Koogle.Web.Store.ClubState.ClubState.Initial with { Clubs = clubs };
var newClub = TestDataGenerator.CreateClubDto();
// Act - Measure time for add operation
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
Koogle.Web.Store.ClubState.ClubReducers.OnCreateClubSuccess(
state,
new Koogle.Web.Store.ClubState.CreateClubSuccessAction(newClub));
}
stopwatch.Stop();
// Assert - 100 reducer calls should complete quickly
stopwatch.ElapsedMilliseconds.Should().BeLessThan(50,
$"100 reducer calls with {clubCount} existing items should complete within 50ms");
}
[Theory]
[InlineData(SmallDataset)]
[InlineData(MediumDataset)]
public void Reducers_DeleteFromLargeList_Efficiently(int clubCount)
{
// Arrange
var clubs = TestDataGenerator.CreateClubDtos(clubCount);
var state = Koogle.Web.Store.ClubState.ClubState.Initial with { Clubs = clubs };
var deleteIds = clubs.Take(100).Select(c => c.Id).ToList();
// Act - Measure time for delete operations
var stopwatch = Stopwatch.StartNew();
foreach (var id in deleteIds)
{
Koogle.Web.Store.ClubState.ClubReducers.OnDeleteClubSuccess(
state,
new Koogle.Web.Store.ClubState.DeleteClubSuccessAction(id));
}
stopwatch.Stop();
// Assert
stopwatch.ElapsedMilliseconds.Should().BeLessThan(100,
$"100 delete operations on {clubCount} items should complete within 100ms");
}
#endregion
#region Memory Allocation Tests
[Fact]
public void LargeDatasetOperations_DoNotCauseExcessiveAllocation()
{
// Arrange
var clubs = TestDataGenerator.CreateClubDtos(MediumDataset);
var state = Koogle.Web.Store.ClubState.ClubState.Initial with { Clubs = clubs };
// Force GC to get baseline
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var memoryBefore = GC.GetTotalMemory(true);
// Act - Perform many operations
for (int i = 0; i < 100; i++)
{
var newClub = TestDataGenerator.CreateClubDto();
Koogle.Web.Store.ClubState.ClubReducers.OnCreateClubSuccess(
state,
new Koogle.Web.Store.ClubState.CreateClubSuccessAction(newClub));
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var memoryAfter = GC.GetTotalMemory(true);
var memoryIncrease = memoryAfter - memoryBefore;
// Assert - Memory increase should be reasonable (< 50MB for 100 operations)
memoryIncrease.Should().BeLessThan(50 * 1024 * 1024,
$"Memory increase should be less than 50MB, was {memoryIncrease / 1024 / 1024}MB");
}
#endregion
}