using Cleanuparr.Domain.Enums; using Cleanuparr.Infrastructure.Events; using Cleanuparr.Infrastructure.Tests.TestHelpers; using Cleanuparr.Persistence; using Cleanuparr.Persistence.Models.Events; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NSubstitute; using Shouldly; using Xunit; namespace Cleanuparr.Infrastructure.Tests.Events; /// /// Integration tests for the cleanup logic that actually deletes events /// public class EventCleanupServiceIntegrationTests : IDisposable { private readonly EventsContext _context; private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; private readonly string _dbName; public EventCleanupServiceIntegrationTests() { _dbName = Guid.NewGuid().ToString(); var services = new ServiceCollection(); // Setup in-memory database services.AddDbContext(options => options.UseInMemoryDatabase(databaseName: _dbName)); _serviceProvider = services.BuildServiceProvider(); _logger = Substitute.For>(); using var scope = _serviceProvider.CreateScope(); _context = scope.ServiceProvider.GetRequiredService(); } public void Dispose() { using var scope = _serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); context.Database.EnsureDeleted(); } [Fact] public async Task CleanupService_PreservesRecentEvents() { // Arrange - Add recent events (within retention period) using (var scope = _serviceProvider.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService(); context.Events.Add(new AppEvent { Id = Guid.NewGuid(), EventType = EventType.QueueItemDeleted, Message = "Recent event 1", Severity = EventSeverity.Information, Timestamp = DateTime.UtcNow.AddDays(-5) }); context.Events.Add(new AppEvent { Id = Guid.NewGuid(), EventType = EventType.DownloadCleaned, Message = "Recent event 2", Severity = EventSeverity.Important, Timestamp = DateTime.UtcNow.AddDays(-10) }); await context.SaveChangesAsync(); } // Verify events exist using (var scope = _serviceProvider.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService(); var count = await context.Events.CountAsync(); count.ShouldBe(2); } } [Fact] public async Task EventCleanupService_CanStartAndStop() { // Arrange var scopeFactory = _serviceProvider.GetRequiredService(); var service = new EventCleanupService(_logger, scopeFactory); var cts = new CancellationTokenSource(); // Act cts.CancelAfter(100); await service.StartAsync(cts.Token); // Give some time for the service to process await Task.Delay(150); await service.StopAsync(CancellationToken.None); // Assert - the service should complete without throwing true.ShouldBeTrue(); } [Fact] public async Task EventCleanupService_HandlesExceptionsGracefully() { // Arrange // Note: In-memory provider doesn't support ExecuteDeleteAsync, // so the cleanup will fail. This test verifies the service handles errors gracefully. var scopeFactory = _serviceProvider.GetRequiredService(); var service = new EventCleanupService(_logger, scopeFactory); var cts = new CancellationTokenSource(); // Act cts.CancelAfter(100); await service.StartAsync(cts.Token); await Task.Delay(150); await service.StopAsync(CancellationToken.None); // Assert - the service should handle the error and continue (log it but not crash) _logger.ReceivedLogContainingAtLeastOnce(LogLevel.Error, "Failed to perform event cleanup"); } }