using Microsoft.Extensions.Configuration; using Moq; using sh.actions.package_cleanup.Models; using sh.actions.package_cleanup.Service; using Xunit; namespace sh.actions.package_cleanup.Tests.Service; public class PackageServiceDateVersionTests { private Mock _configurationMock; private Mock _giteaPackageServiceMock; private IPackageService _packageService; private void SetupMocks() { _configurationMock = new Mock(); _giteaPackageServiceMock = new Mock(); _packageService = new PackageService(_configurationMock.Object, _giteaPackageServiceMock.Object); } // ...existing code... #region Version Detection Tests [Theory] [InlineData("2024.12.25.1")] [InlineData("2025.2.14.1")] [InlineData("2026.1.1.100")] [InlineData("2023.12.31.999")] public void FilterPackagesToDelete_WithDateVersions_ShouldIdentifyCorrectly(string version) { // Arrange SetupMocks(); var packages = new List { new() { Id = 1, Name = "test", Version = version, Type = "docker", CreatedAt = DateTime.UtcNow } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Single prod date version should not be deleted Assert.NotNull(result); Assert.Empty(result); } [Theory] [InlineData("2024.12.25.1-dev")] [InlineData("2025.2.14.1-alpha")] [InlineData("2026.1.1.100-beta")] [InlineData("2023.12.31.999-rc")] public void FilterPackagesToDelete_WithDateVersionsAndSuffixes_ShouldIdentifyCorrectly(string version) { // Arrange SetupMocks(); var packages = new List { new() { Id = 1, Name = "test", Version = version, Type = "docker", CreatedAt = DateTime.UtcNow } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Single prerelease/dev date version should be kept Assert.NotNull(result); Assert.Empty(result); } #endregion #region Prod Release Tests - Date Versioning [Fact] public void FilterPackagesToDeleteByDateVersion_WithOnlyProdReleases_ShouldReturnEmpty() { // Arrange - CLEAN_PROD_VERSIONS not set, defaults to false SetupMocks(); var packages = new List { new() { Id = 1, Name = "test", Version = "2025.12.20.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-30) }, new() { Id = 2, Name = "test", Version = "2025.12.25.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-20) }, new() { Id = 3, Name = "test", Version = "2026.1.5.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-10) } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Prod releases should be protected by default (CLEAN_PROD_VERSIONS=false) Assert.Empty(result); } [Fact] public void FilterPackagesToDeleteByDateVersion_WithProdReleasesAndCleaningEnabled_ShouldDeleteOldest() { // Arrange SetupMocks(); _configurationMock.Setup(c => c["CLEAN_PROD_VERSIONS"]).Returns("true"); _configurationMock.Setup(c => c["KEEP_PROD_VERSIONS"]).Returns("2"); var packages = new List { new() { Id = 1, Name = "test", Version = "2025.1.1.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-365) }, new() { Id = 2, Name = "test", Version = "2025.6.15.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-200) }, new() { Id = 3, Name = "test", Version = "2026.1.1.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-45) }, new() { Id = 4, Name = "test", Version = "2026.2.14.1", Type = "docker", CreatedAt = DateTime.UtcNow } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Should delete oldest 2 (2025.1.1 and 2025.6.15), keep newest 2 Assert.Equal(2, result.Count); Assert.Contains(result, p => p.Id == 1); Assert.Contains(result, p => p.Id == 2); Assert.DoesNotContain(result, p => p.Id == 3 || p.Id == 4); } [Fact] public void FilterPackagesToDeleteByDateVersion_WithMultipleProdVersions_ShouldKeepNewest() { // Arrange SetupMocks(); var packages = new List { new() { Id = 1, Name = "test", Version = "2025.1.1.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-365) }, new() { Id = 2, Name = "test", Version = "2025.6.15.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-200) }, new() { Id = 3, Name = "test", Version = "2026.1.1.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-45) }, new() { Id = 4, Name = "test", Version = "2026.2.14.1", Type = "docker", CreatedAt = DateTime.UtcNow } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Default is CLEAN_PROD_VERSIONS=false, so nothing should be deleted Assert.Empty(result); } #endregion #region Prerelease Tests - Date Versioning [Fact] public void FilterPackagesToDeleteByDateVersion_WithPrereleaseVersions_ShouldKeepLatest() { // Arrange - Default KEEP_PRERELEASE_VERSIONS=3 SetupMocks(); var packages = new List { new() { Id = 1, Name = "test", Version = "2026.2.10.1-alpha", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-5) }, new() { Id = 2, Name = "test", Version = "2026.2.11.1-beta", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-4) }, new() { Id = 3, Name = "test", Version = "2026.2.12.1-rc", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-3) }, new() { Id = 4, Name = "test", Version = "2026.2.13.1-preview", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-2) } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Should delete oldest 1 (alpha), keep newest 3 Assert.Single(result); Assert.Contains(result, p => p.Id == 1); // alpha is oldest Assert.DoesNotContain(result, p => p.Id == 2 || p.Id == 3 || p.Id == 4); } [Fact] public void FilterPackagesToDeleteByDateVersion_WithManyPrereleases_ShouldEnforceLimit() { // Arrange - Custom KEEP_PRERELEASE_VERSIONS=2 SetupMocks(); _configurationMock.Setup(c => c["KEEP_PRERELEASE_VERSIONS"]).Returns("2"); var packages = new List(); for (int i = 0; i < 10; i++) { var date = DateTime.UtcNow.AddDays(-i); packages.Add(new GiteaPackage { Id = i, Name = "test", Version = $"{date.Year}.{date.Month}.{date.Day}.{i}-beta", Type = "docker", CreatedAt = date }); } // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Should delete 8 oldest, keep only 2 newest (Id=0,1) Assert.Equal(8, result.Count); Assert.DoesNotContain(result, p => p.Id < 2); Assert.Contains(result, p => p.Id >= 2); } [Fact] public void FilterPackagesToDeleteByDateVersion_WithMixedPrereleases_ShouldOrderByDate() { // Arrange SetupMocks(); var packages = new List { new() { Id = 1, Name = "test", Version = "2025.12.1.1-beta", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-60) }, new() { Id = 2, Name = "test", Version = "2026.1.15.1-beta", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-30) }, new() { Id = 3, Name = "test", Version = "2026.2.1.1-alpha", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-14) } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Default keep 3, so all should be kept Assert.Empty(result); } #endregion #region Dev Release Tests - Date Versioning [Fact] public void FilterPackagesToDeleteByDateVersion_WithDevVersions_ShouldKeepLatest() { // Arrange - Default KEEP_DEV_VERSIONS=5 SetupMocks(); var packages = new List { new() { Id = 1, Name = "test", Version = "2026.2.10.1-dev", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-5) }, new() { Id = 2, Name = "test", Version = "2026.2.11.2-dev", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-4) }, new() { Id = 3, Name = "test", Version = "2026.2.12.3-dev", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-3) } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Default keep 5, so all 3 should be kept Assert.Empty(result); } [Fact] public void FilterPackagesToDeleteByDateVersion_WithManyDailyDevReleases_ShouldEnforceLimit() { // Arrange - Custom KEEP_DEV_VERSIONS=3 SetupMocks(); _configurationMock.Setup(c => c["KEEP_DEV_VERSIONS"]).Returns("3"); var packages = new List(); for (int i = 0; i < 30; i++) { var date = DateTime.UtcNow.AddDays(-i); packages.Add(new GiteaPackage { Id = i, Name = "test", Version = $"{date.Year}.{date.Month}.{date.Day}.{i}-dev", Type = "docker", CreatedAt = date }); } // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Should delete 27 oldest, keep only 3 newest (Id=0,1,2) Assert.Equal(27, result.Count); Assert.DoesNotContain(result, p => p.Id < 3); Assert.Contains(result, p => p.Id >= 3); } [Fact] public void FilterPackagesToDeleteByDateVersion_WithManyDailyDevReleasesDefaultLimit_ShouldIdentifyAll() { // Arrange SetupMocks(); var packages = new List(); for (int i = 0; i < 30; i++) { var date = DateTime.UtcNow.AddDays(-i); packages.Add(new GiteaPackage { Id = i, Name = "test", Version = $"{date.Year}.{date.Month}.{date.Day}.{i}-dev", Type = "docker", CreatedAt = date }); } // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Default keep 5, so 25 should be deleted Assert.Equal(25, result.Count); Assert.DoesNotContain(result, p => p.Id < 5); // Keep newest 5 (Id 0-4) Assert.Contains(result, p => p.Id >= 5); } #endregion #region Mixed Release Types - Date Versioning [Fact] public void FilterPackagesToDeleteByDateVersion_WithMixedReleaseTypes_ShouldSeparateAndCleanCorrectly() { // Arrange SetupMocks(); _configurationMock.Setup(c => c["KEEP_PRERELEASE_VERSIONS"]).Returns("2"); _configurationMock.Setup(c => c["KEEP_DEV_VERSIONS"]).Returns("2"); var packages = new List { // Prod releases (should not be deleted - CLEAN_PROD_VERSIONS=false) new() { Id = 1, Name = "test", Version = "2025.12.1.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-80) }, new() { Id = 2, Name = "test", Version = "2026.1.1.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-45) }, // Prerelease (keep 2 newest) new() { Id = 3, Name = "test", Version = "2026.1.25.1-alpha", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-20) }, new() { Id = 4, Name = "test", Version = "2026.2.1.1-beta", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-14) }, new() { Id = 5, Name = "test", Version = "2026.2.5.1-rc", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-10) }, // Dev (keep 2 newest) new() { Id = 6, Name = "test", Version = "2026.2.10.1-dev", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-5) }, new() { Id = 7, Name = "test", Version = "2026.2.14.1-dev", Type = "docker", CreatedAt = DateTime.UtcNow } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert Assert.Equal(1, result.Count); // Should delete 1 prerelease (alpha), 0 dev (keep both) Assert.DoesNotContain(result, p => p.Id <= 2); // Prod not deleted Assert.DoesNotContain(result, p => p.Id >= 6); // Keep 2 latest dev } [Fact] public void FilterPackagesToDeleteByDateVersion_WithLargeSet_ShouldHandleCorrectly() { // Arrange SetupMocks(); _configurationMock.Setup(c => c["CLEAN_PROD_VERSIONS"]).Returns("false"); _configurationMock.Setup(c => c["KEEP_PRERELEASE_VERSIONS"]).Returns("3"); _configurationMock.Setup(c => c["KEEP_DEV_VERSIONS"]).Returns("5"); var packages = new List(); // Add prod releases for (int i = 0; i < 5; i++) { var date = DateTime.UtcNow.AddMonths(-(5 - i)); packages.Add(new GiteaPackage { Id = i, Name = "test", Version = $"{date.Year}.{date.Month}.1.1", Type = "docker", CreatedAt = date }); } // Add prerelease for (int i = 5; i < 15; i++) { var date = DateTime.UtcNow.AddDays(-(15 - i) * 7); packages.Add(new GiteaPackage { Id = i, Name = "test", Version = $"{date.Year}.{date.Month}.{date.Day}.1-beta", Type = "docker", CreatedAt = date }); } // Add dev for (int i = 15; i < 30; i++) { var date = DateTime.UtcNow.AddDays(-(30 - i)); packages.Add(new GiteaPackage { Id = i, Name = "test", Version = $"{date.Year}.{date.Month}.{date.Day}.{(30 - i)}-dev", Type = "docker", CreatedAt = date }); } // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert Assert.NotNull(result); // Prod releases should never be deleted Assert.DoesNotContain(result, p => p.Id < 5); // Should delete 7 oldest prerelease (keep 3 newest) var prereleaseDeleted = result.Where(p => p.Id >= 5 && p.Id < 15).ToList(); Assert.Equal(7, prereleaseDeleted.Count); // Should delete 10 oldest dev (keep 5 newest) var devDeleted = result.Where(p => p.Id >= 15).ToList(); Assert.Equal(10, devDeleted.Count); } #endregion #region Edge Cases - Date Versioning [Theory] [InlineData("1000.1.1.1")] // Minimum valid year [InlineData("2999.12.31.999")] // Maximum valid year [InlineData("2025.2.14.0")] // Build number 0 public void FilterPackagesToDeleteByDateVersion_WithBoundaryVersions_ShouldHandleCorrectly(string version) { // Arrange SetupMocks(); var packages = new List { new() { Id = 1, Name = "test", Version = version, Type = "docker", CreatedAt = DateTime.UtcNow } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert Assert.NotNull(result); } [Theory] [InlineData("999.12.31.1")] // Year too small [InlineData("3000.1.1.1")] // Year too large [InlineData("2025.13.1.1")] // Month out of range [InlineData("2025.0.1.1")] // Month zero [InlineData("2025.2.32.1")] // Day out of range [InlineData("2025.2.0.1")] // Day zero public void FilterPackagesToDelete_WithInvalidDateVersions_ShouldNotProcess(string version) { // Arrange SetupMocks(); var packages = new List { new() { Id = 1, Name = "test", Version = version, Type = "docker", CreatedAt = DateTime.UtcNow }, new() { Id = 2, Name = "test", Version = "1.0.0.1", Type = "docker", CreatedAt = DateTime.UtcNow } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert - Invalid date version should be skipped, only semantic version processed Assert.NotNull(result); } [Fact] public void FilterPackagesToDeleteByDateVersion_WithConsecutiveDates_ShouldOrderCorrectly() { // Arrange SetupMocks(); var packages = new List { new() { Id = 1, Name = "test", Version = "2025.2.10.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-5) }, new() { Id = 2, Name = "test", Version = "2025.2.11.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-4) }, new() { Id = 3, Name = "test", Version = "2025.2.12.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-3) }, new() { Id = 4, Name = "test", Version = "2025.2.13.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-2) }, new() { Id = 5, Name = "test", Version = "2025.2.14.1", Type = "docker", CreatedAt = DateTime.UtcNow } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert Assert.NotNull(result); } #endregion #region Mixed Versioning Variants [Fact] public void FilterPackagesToDelete_WithMixedVersioningVariants_ShouldProcessBothTypes() { // Arrange SetupMocks(); var packages = new List { // Semantic versioning new() { Id = 1, Name = "test", Version = "1.0.0", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-30) }, new() { Id = 2, Name = "test", Version = "1.1.0-dev", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-10) }, // Date versioning new() { Id = 3, Name = "test", Version = "2025.1.1.1", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-45) }, new() { Id = 4, Name = "test", Version = "2025.2.10.1-dev", Type = "docker", CreatedAt = DateTime.UtcNow.AddDays(-5) } }; // Act var result = _packageService.FilterPackagesToDelete(packages); // Assert Assert.NotNull(result); // Prod releases should not be in delete list Assert.DoesNotContain(result, p => p.Id == 1 || p.Id == 3); } #endregion }