From 452e7375c531e9dc18f25b67d6c28d4c037e5931 Mon Sep 17 00:00:00 2001 From: Marcin Torba Date: Thu, 12 Sep 2024 00:28:45 +0200 Subject: [PATCH] Tests --- .../Config/AzureDevOpsConfig.cs | 2 +- src/DependencyUpdated.Core/Config/Project.cs | 4 +- .../AzureDevOps.cs | 2 + .../AzueDevOpsConfigTest.cs | 69 ++++++++++++++++ .../BaseConfigTest.cs | 26 ++++++ .../ProjectConfigTest.cs | 56 +++++++++++++ ...cyUpdated.Projects.DotNet.UnitTests.csproj | 6 ++ .../DotNetUpdaterTests.cs | 82 ++++++++++++++++++- .../MockMemoryCache.cs | 60 ++++++++++++++ .../Projects/Nuget.config | 7 ++ 10 files changed, 308 insertions(+), 6 deletions(-) create mode 100644 tests/DependencyUpdated.Core.UnitTests/AzueDevOpsConfigTest.cs create mode 100644 tests/DependencyUpdated.Core.UnitTests/BaseConfigTest.cs create mode 100644 tests/DependencyUpdated.Core.UnitTests/ProjectConfigTest.cs create mode 100644 tests/DependencyUpdated.Projects.DotNet.UnitTests/MockMemoryCache.cs create mode 100644 tests/DependencyUpdated.Projects.DotNet.UnitTests/Projects/Nuget.config diff --git a/src/DependencyUpdated.Core/Config/AzureDevOpsConfig.cs b/src/DependencyUpdated.Core/Config/AzureDevOpsConfig.cs index 7f654c6..6b2b7f9 100644 --- a/src/DependencyUpdated.Core/Config/AzureDevOpsConfig.cs +++ b/src/DependencyUpdated.Core/Config/AzureDevOpsConfig.cs @@ -2,7 +2,7 @@ namespace DependencyUpdated.Core.Config; -public class AzureDevOpsConfig : IValidatableObject +public record AzureDevOpsConfig : IValidatableObject { public string? Username { get; set; } diff --git a/src/DependencyUpdated.Core/Config/Project.cs b/src/DependencyUpdated.Core/Config/Project.cs index 086e8a7..3f98137 100644 --- a/src/DependencyUpdated.Core/Config/Project.cs +++ b/src/DependencyUpdated.Core/Config/Project.cs @@ -3,7 +3,7 @@ namespace DependencyUpdated.Core.Config; -public sealed class Project : IValidatableObject +public sealed record Project : IValidatableObject { public ProjectType Type { get; set; } @@ -55,7 +55,7 @@ public IEnumerable Validate(ValidationContext validationContex if (Groups.Count == 0) { - yield return new ValidationResult($"Missing ${nameof(Groups)}."); + yield return new ValidationResult($"Missing {nameof(Groups)}."); } } diff --git a/src/DependencyUpdated.Repositories.AzureDevOps/AzureDevOps.cs b/src/DependencyUpdated.Repositories.AzureDevOps/AzureDevOps.cs index de029b2..58cb32e 100644 --- a/src/DependencyUpdated.Repositories.AzureDevOps/AzureDevOps.cs +++ b/src/DependencyUpdated.Repositories.AzureDevOps/AzureDevOps.cs @@ -6,6 +6,7 @@ using LibGit2Sharp.Handlers; using Microsoft.Extensions.Options; using Serilog; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Http.Headers; using System.Text; @@ -13,6 +14,7 @@ namespace DependencyUpdated.Repositories.AzureDevOps; +[ExcludeFromCodeCoverage] internal sealed class AzureDevOps(TimeProvider timeProvider, IOptions config, ILogger logger) : IRepositoryProvider { diff --git a/tests/DependencyUpdated.Core.UnitTests/AzueDevOpsConfigTest.cs b/tests/DependencyUpdated.Core.UnitTests/AzueDevOpsConfigTest.cs new file mode 100644 index 0000000..462d72d --- /dev/null +++ b/tests/DependencyUpdated.Core.UnitTests/AzueDevOpsConfigTest.cs @@ -0,0 +1,69 @@ +using DependencyUpdated.Core.Config; +using DependencyUpdated.Core.Models.Enums; +using System.ComponentModel.DataAnnotations; + +namespace DependencyUpdated.Core.UnitTests; + +public class AzureDevOpsConfigTest : BaseConfigTest +{ + protected override IEnumerable>> TestCases + { + get + { + yield return Tuple.Create>(CreateValidConfig(), + ArraySegment.Empty); + yield return Tuple.Create>( + CreateValidConfig() with { Username = string.Empty }, + new[] + { + new ValidationResult( + $"{nameof(AzureDevOpsConfig.Username)} must be provided in {nameof(AzureDevOpsConfig)}") + }); + yield return Tuple.Create>( + CreateValidConfig() with { Email = string.Empty }, + new[] + { + new ValidationResult( + $"{nameof(AzureDevOpsConfig.Email)} must be provided in {nameof(AzureDevOpsConfig)}") + }); + yield return Tuple.Create>( + CreateValidConfig() with { Organization = string.Empty }, + new[] + { + new ValidationResult( + $"{nameof(AzureDevOpsConfig.Organization)} must be provided in {nameof(AzureDevOpsConfig)}") + }); + yield return Tuple.Create>( + CreateValidConfig() with { Project = string.Empty }, + new[] + { + new ValidationResult( + $"{nameof(AzureDevOpsConfig.Project)} must be provided in {nameof(AzureDevOpsConfig)}") + }); + yield return Tuple.Create>( + CreateValidConfig() with { Repository = string.Empty }, + new[] + { + new ValidationResult( + $"{nameof(AzureDevOpsConfig.Repository)} must be provided in {nameof(AzureDevOpsConfig)}") + }); + } + } + + private static AzureDevOpsConfig CreateValidConfig() + { + return new AzureDevOpsConfig() + { + Username = "User", + Email = "Email", + Project = "Project", + Organization = "Organization", + Repository = "Repository", + AutoComplete = true, + BranchName = "Branch", + PAT = "PAT", + TargetBranchName = "TargetBranch", + WorkItemId = 1500 + }; + } +} \ No newline at end of file diff --git a/tests/DependencyUpdated.Core.UnitTests/BaseConfigTest.cs b/tests/DependencyUpdated.Core.UnitTests/BaseConfigTest.cs new file mode 100644 index 0000000..8bdde72 --- /dev/null +++ b/tests/DependencyUpdated.Core.UnitTests/BaseConfigTest.cs @@ -0,0 +1,26 @@ +using FluentAssertions; +using FluentAssertions.Execution; +using System.ComponentModel.DataAnnotations; +using Xunit; + +namespace DependencyUpdated.Core.UnitTests; + +public abstract class BaseConfigTest +{ + protected abstract IEnumerable>> TestCases { get; } + + [Fact] + public void Validate() + { + var data = TestCases.ToList(); + using (new AssertionScope()) + { + foreach (var test in data) + { + var context = new ValidationContext(test.Item1); + var result = test.Item1.Validate(context); + result.Should().BeEquivalentTo(test.Item2); + } + } + } +} \ No newline at end of file diff --git a/tests/DependencyUpdated.Core.UnitTests/ProjectConfigTest.cs b/tests/DependencyUpdated.Core.UnitTests/ProjectConfigTest.cs new file mode 100644 index 0000000..e0264f4 --- /dev/null +++ b/tests/DependencyUpdated.Core.UnitTests/ProjectConfigTest.cs @@ -0,0 +1,56 @@ +using DependencyUpdated.Core.Config; +using DependencyUpdated.Core.Models.Enums; +using System.ComponentModel.DataAnnotations; + +namespace DependencyUpdated.Core.UnitTests; + +public class ProjectConfigTest : BaseConfigTest +{ + protected override IEnumerable>> TestCases + { + get + { + yield return Tuple.Create>(CreateValidProject(), + ArraySegment.Empty); + yield return Tuple.Create>( + CreateValidProject() with { Directories = ArraySegment.Empty }, + new[]{ new ValidationResult($"{nameof(Project.Directories)} cannot be empty")}); + yield return Tuple.Create>( + CreateValidProject() with { Directories = ["TestPath"] }, + new[]{ new ValidationResult("Path TestPath not found")}); + yield return Tuple.Create>( + CreateValidProject() with { EachDirectoryAsSeparate = false, Name = string.Empty }, + new[] + { + new ValidationResult( + $"{nameof(Project.Name)} must be provided when {nameof(Project.EachDirectoryAsSeparate)} is not set") + }); + yield return Tuple.Create>( + CreateValidProject() with { EachDirectoryAsSeparate = true, Name = "TestName" }, + new[] + { + new ValidationResult( + $"{nameof(Project.Name)} must not be provided when {nameof(Project.EachDirectoryAsSeparate)} is set") + }); + yield return Tuple.Create>( + CreateValidProject() with { Groups = ArraySegment.Empty }, + new[]{ new ValidationResult($"Missing {nameof(Project.Groups)}.")}); + } + } + + private static Project CreateValidProject() + { + return new Project() + { + DependencyConfigurations = new[] { "Test" }, + Directories = new[] { Environment.CurrentDirectory }, + Exclude = new[] { "Exclude" }, + Include = new[] { "Include" }, + Groups = new[] { "Groups" }, + Name = "Name", + Type = ProjectType.DotNet, + Version = VersionUpdateType.Major, + EachDirectoryAsSeparate = false + }; + } +} \ No newline at end of file diff --git a/tests/DependencyUpdated.Projects.DotNet.UnitTests/DependencyUpdated.Projects.DotNet.UnitTests.csproj b/tests/DependencyUpdated.Projects.DotNet.UnitTests/DependencyUpdated.Projects.DotNet.UnitTests.csproj index bc4025a..30f65cd 100644 --- a/tests/DependencyUpdated.Projects.DotNet.UnitTests/DependencyUpdated.Projects.DotNet.UnitTests.csproj +++ b/tests/DependencyUpdated.Projects.DotNet.UnitTests/DependencyUpdated.Projects.DotNet.UnitTests.csproj @@ -19,4 +19,10 @@ + + + Always + + + diff --git a/tests/DependencyUpdated.Projects.DotNet.UnitTests/DotNetUpdaterTests.cs b/tests/DependencyUpdated.Projects.DotNet.UnitTests/DotNetUpdaterTests.cs index d4753bf..c53cb06 100644 --- a/tests/DependencyUpdated.Projects.DotNet.UnitTests/DotNetUpdaterTests.cs +++ b/tests/DependencyUpdated.Projects.DotNet.UnitTests/DotNetUpdaterTests.cs @@ -3,7 +3,6 @@ using DependencyUpdated.Core.Models.Enums; using FluentAssertions; using FluentAssertions.Execution; -using Microsoft.Extensions.Caching.Memory; using NSubstitute; using Serilog; using Xunit; @@ -14,13 +13,13 @@ public class DotNetUpdaterTests { private readonly DotNetUpdater _target; private readonly ILogger _logger; - private readonly IMemoryCache _memoryCahce; + private readonly MockMemoryCache _memoryCahce; private readonly string _searchPath; public DotNetUpdaterTests() { _logger = Substitute.For(); - _memoryCahce = Substitute.For(); + _memoryCahce = new MockMemoryCache(); _target = new DotNetUpdater(_logger, _memoryCahce); _searchPath = "Projects"; } @@ -63,4 +62,81 @@ public async Task ExtractAllPackages_Should_ReturnPackagesFromCsProjFile() packages.Should().BeEquivalentTo(expectedResult); } } + + [Fact] + public async Task GetVersions_Should_ReturnVersionFromCache() + { + // Arrange + var packages = new DependencyDetails("TestName", new Version(1, 0, 0)); + var projectConfiguration = new Project(); + var cacheData = new List { new("TestName", new Version(1, 2, 3)) }; + _memoryCahce.AddEntry(packages.Name, cacheData); + + // Act + var result = await _target.GetVersions(packages, projectConfiguration); + + // Assert + using (new AssertionScope()) + { + result.Should().BeEquivalentTo(cacheData); + } + } + + [Fact] + public async Task GetVersions_Should_ThrowForMissingDependencyConfiguration() + { + // Arrange + var packages = new DependencyDetails("TestName", new Version(1, 0, 0)); + var projectConfiguration = new Project(); + + // Act, Assert + await _target.Awaiting(x => x.GetVersions(packages, projectConfiguration)).Should() + .ThrowExactlyAsync(); + } + + [Fact] + public async Task GetVersions_Should_GetPackagesFromSource() + { + // Arrange + var packages = new DependencyDetails("Serilog", new Version(1, 0, 0)); + var projectConfiguration = new Project() { Type = ProjectType.DotNet }; + projectConfiguration.ApplyDefaultValue(); + projectConfiguration.DependencyConfigurations = projectConfiguration.DependencyConfigurations + .Concat(new[] { "./Projects/Nuget.config" }).ToArray(); + + // Act + var result = await _target.GetVersions(packages, projectConfiguration); + + // Assert + using (new AssertionScope()) + { + result.Should().NotBeNullOrEmpty(); + result.Should().ContainEquivalentOf(new DependencyDetails("Serilog", new Version(4, 0, 1, 0))); + } + } + + [Fact] + public async Task UpdateCsProj_Should_UpdateVersion() + { + // Arrange + var projectToUpdate = "testProj.csproj"; + if (File.Exists(projectToUpdate)) + { + File.Delete(projectToUpdate); + } + File.Copy("./Projects/SampleProject.csproj", projectToUpdate); + + // Act + var result = _target.HandleProjectUpdate([projectToUpdate], + new List() { new("Serilog", new Version(4, 0, 0)) }); + + // Assert + using (new AssertionScope()) + { + result.Should().ContainEquivalentOf(new UpdateResult("Serilog", "3.0.0", "4.0.0")); + var packagesFromfile = await _target.ExtractAllPackages([projectToUpdate]); + packagesFromfile.Should().ContainEquivalentOf(new DependencyDetails("Serilog", new Version(4, 0, 0, 0))); + File.Delete(projectToUpdate); + } + } } diff --git a/tests/DependencyUpdated.Projects.DotNet.UnitTests/MockMemoryCache.cs b/tests/DependencyUpdated.Projects.DotNet.UnitTests/MockMemoryCache.cs new file mode 100644 index 0000000..7db9bc3 --- /dev/null +++ b/tests/DependencyUpdated.Projects.DotNet.UnitTests/MockMemoryCache.cs @@ -0,0 +1,60 @@ +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Primitives; + +namespace DependencyUpdated.Projects.DotNet.UnitTests; + +internal sealed class MockMemoryCache : IMemoryCache +{ + private Dictionary Cache { get; } = new(); + + public void Dispose() + { + } + + public bool TryGetValue(object key, out object? value) + { + var exists = Cache.TryGetValue(key, out var dictValue); + value = dictValue?.Value; + return exists; + } + + public ICacheEntry CreateEntry(object key) + { + return new MockEntry() { Key = key }; + } + + public void AddEntry(object key, object value) + { + Cache.Add(key, new MockEntry() { Key = key, Value = value }); + } + + public void Remove(object key) + { + Cache.Remove(key); + } +} + +internal sealed class MockEntry : ICacheEntry +{ + public void Dispose() + { + } + + public object Key { get; init; } + + public object? Value { get; set; } + + public DateTimeOffset? AbsoluteExpiration { get; set; } + + public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; } + + public TimeSpan? SlidingExpiration { get; set; } + + public IList ExpirationTokens { get; } + + public IList PostEvictionCallbacks { get; } + + public CacheItemPriority Priority { get; set; } + + public long? Size { get; set; } +} \ No newline at end of file diff --git a/tests/DependencyUpdated.Projects.DotNet.UnitTests/Projects/Nuget.config b/tests/DependencyUpdated.Projects.DotNet.UnitTests/Projects/Nuget.config new file mode 100644 index 0000000..322a22e --- /dev/null +++ b/tests/DependencyUpdated.Projects.DotNet.UnitTests/Projects/Nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file