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 }