diff --git a/README.md b/README.md index 22e13bf..e10503c 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,11 @@ A simple measure of dependency freshness `-q`, `--quiet`: only show outdated packages `-u`, `--update`: update project files after displaying packages + +#### Limits: + +`-l`, `--limit`: exits with error code if total libyears behind is greater than this value + +`-p`, `--limit-project`: exits with error code if any project is more libyears behind than this value + +`-a`, `--limit-any`: exits with error code if any dependency is more libyears behind than this value \ No newline at end of file diff --git a/src/LibYear.Core/FileTypes/IProjectFile.cs b/src/LibYear.Core/FileTypes/IProjectFile.cs index e1e8402..bdb992d 100644 --- a/src/LibYear.Core/FileTypes/IProjectFile.cs +++ b/src/LibYear.Core/FileTypes/IProjectFile.cs @@ -4,5 +4,5 @@ public interface IProjectFile { string FileName { get; } IDictionary Packages { get; } - string Update(IEnumerable results); + string Update(IReadOnlyCollection results); } \ No newline at end of file diff --git a/src/LibYear.Core/FileTypes/XmlProjectFile.cs b/src/LibYear.Core/FileTypes/XmlProjectFile.cs index 3e6d2e3..184a38a 100644 --- a/src/LibYear.Core/FileTypes/XmlProjectFile.cs +++ b/src/LibYear.Core/FileTypes/XmlProjectFile.cs @@ -28,7 +28,7 @@ protected XmlProjectFile(string filename, string contents, string elementName, s ); } - public string Update(IEnumerable results) + public string Update(IReadOnlyCollection results) { foreach (var result in results.Where(r => r.Latest != null)) { @@ -68,12 +68,11 @@ private void UpdateElement(XElement element, string latestVersion) e.Value = latestVersion; } - private IEnumerable GetMatchingElements(Result result) - { - return _xmlContents.Descendants(_elementName) + private IReadOnlyCollection GetMatchingElements(Result result) + => _xmlContents.Descendants(_elementName) .Where(d => _packageAttributeNames.Any(attributeName => (d.Attribute(attributeName)?.Value ?? d.Element(attributeName)?.Value) == result.Name && (d.Attribute(_versionAttributeName)?.Value ?? d.Element(_versionAttributeName)?.Value) == result.Installed?.Version.ToString() ) - ); - } + ) + .ToArray(); } \ No newline at end of file diff --git a/src/LibYear.Core/HasAgeMeasurements.cs b/src/LibYear.Core/HasAgeMeasurements.cs new file mode 100644 index 0000000..5946eda --- /dev/null +++ b/src/LibYear.Core/HasAgeMeasurements.cs @@ -0,0 +1,10 @@ +namespace LibYear.Core; + +public abstract class HasAgeMeasurements +{ + public double YearsBehind => DaysBehind > 0 + ? DaysBehind / 365 + : 0; + + public abstract double DaysBehind { get; } +} \ No newline at end of file diff --git a/src/LibYear.Core/IPackageVersionChecker.cs b/src/LibYear.Core/IPackageVersionChecker.cs index 544a0d2..4fae347 100644 --- a/src/LibYear.Core/IPackageVersionChecker.cs +++ b/src/LibYear.Core/IPackageVersionChecker.cs @@ -4,5 +4,5 @@ namespace LibYear.Core; public interface IPackageVersionChecker { - Task>> GetPackages(IEnumerable projectFiles); + Task GetPackages(IReadOnlyCollection projectFiles); } \ No newline at end of file diff --git a/src/LibYear.Core/IProjectFileManager.cs b/src/LibYear.Core/IProjectFileManager.cs index 53e531e..1284e44 100644 --- a/src/LibYear.Core/IProjectFileManager.cs +++ b/src/LibYear.Core/IProjectFileManager.cs @@ -4,6 +4,6 @@ namespace LibYear.Core; public interface IProjectFileManager { - Task> GetAllProjects(IReadOnlyList paths); - Task> Update(IDictionary> allResults); + Task> GetAllProjects(IReadOnlyCollection paths); + Task> Update(SolutionResult result); } \ No newline at end of file diff --git a/src/LibYear.Core/LibYear.Core.csproj b/src/LibYear.Core/LibYear.Core.csproj index cb44259..63262a2 100644 --- a/src/LibYear.Core/LibYear.Core.csproj +++ b/src/LibYear.Core/LibYear.Core.csproj @@ -1,8 +1,7 @@ - 6.0.1 - netstandard2.0 - latest + 7.0.0 + net6.0 libyear LibYear.Core ecoAPM LLC diff --git a/src/LibYear.Core/PackageVersion.cs b/src/LibYear.Core/PackageVersion.cs index dce5c36..4f8f664 100644 --- a/src/LibYear.Core/PackageVersion.cs +++ b/src/LibYear.Core/PackageVersion.cs @@ -42,8 +42,7 @@ public override string ToString() public override string ToString(string format, IFormatProvider? formatProvider) { - if (formatProvider == null - || !TryFormatter(format, formatProvider, out var formattedString)) + if (formatProvider == null || !TryFormatter(format, formatProvider, out var formattedString)) { formattedString = ToString(); } diff --git a/src/LibYear.Core/PackageVersionChecker.cs b/src/LibYear.Core/PackageVersionChecker.cs index 34f8569..f2dec02 100644 --- a/src/LibYear.Core/PackageVersionChecker.cs +++ b/src/LibYear.Core/PackageVersionChecker.cs @@ -8,30 +8,31 @@ namespace LibYear.Core; public class PackageVersionChecker : IPackageVersionChecker { private readonly PackageMetadataResource _metadataResource; - private readonly IDictionary> _versionCache; + private readonly IDictionary> _versionCache; public PackageVersionChecker(PackageMetadataResource metadataResource) - : this(metadataResource, new ConcurrentDictionary>()) + : this(metadataResource, new ConcurrentDictionary>()) { } - public PackageVersionChecker(PackageMetadataResource metadataResource, IDictionary> versionCache) + public PackageVersionChecker(PackageMetadataResource metadataResource, IDictionary> versionCache) { _metadataResource = metadataResource; _versionCache = versionCache; } - public async Task>> GetPackages(IEnumerable projectFiles) + public async Task GetPackages(IReadOnlyCollection projectFiles) { var tasks = projectFiles.ToDictionary(proj => proj, GetResults); - await Task.WhenAll(tasks.Values); - return tasks.ToDictionary(p => p.Key, p => p.Value.GetAwaiter().GetResult()); + var results = await Task.WhenAll(tasks.Values); + return new SolutionResult(results); } - private async Task> GetResults(IProjectFile proj) + private async Task GetResults(IProjectFile proj) { - var results = proj.Packages.Select(p => GetResult(p.Key, p.Value)); - return await Task.WhenAll(results); + var tasks = proj.Packages.Select(p => GetResult(p.Key, p.Value)); + var results = await Task.WhenAll(tasks); + return new ProjectResult(proj, results); } public async Task GetResult(string packageName, PackageVersion? installed) @@ -52,9 +53,9 @@ public async Task GetResult(string packageName, PackageVersion? installe return new Result(packageName, current, latest); } - public async Task> GetVersions(string packageName) + public async Task> GetVersions(string packageName) { var metadata = await _metadataResource.GetMetadataAsync(packageName, true, true, NullSourceCacheContext.Instance, NullLogger.Instance, CancellationToken.None); - return metadata.Select(m => new Release(m)).ToList(); + return metadata.Select(m => new Release(m)).ToArray(); } } \ No newline at end of file diff --git a/src/LibYear.Core/ProjectFileManager.cs b/src/LibYear.Core/ProjectFileManager.cs index c2c0ab9..3b7a742 100644 --- a/src/LibYear.Core/ProjectFileManager.cs +++ b/src/LibYear.Core/ProjectFileManager.cs @@ -10,17 +10,17 @@ public class ProjectFileManager : IProjectFileManager public ProjectFileManager(IFileSystem fileSystem) => _fileSystem = fileSystem; - public async Task> GetAllProjects(IReadOnlyList paths) + public async Task> GetAllProjects(IReadOnlyCollection paths) { if (!paths.Any()) return await GetProjectsInDir(Directory.GetCurrentDirectory()); var tasks = paths.Select(GetProjects); var projects = await Task.WhenAll(tasks); - return projects.SelectMany(p => p).ToList(); + return projects.SelectMany(p => p).ToArray(); } - private async Task> GetProjects(string path) + private async Task> GetProjects(string path) { if (_fileSystem.Directory.Exists(path)) { @@ -28,10 +28,10 @@ private async Task> GetProjects(string path) } var fileInfo = _fileSystem.FileInfo.FromFileName(path); - return new[] { await ReadFile(fileInfo) }.Where(f => f != null).ToList(); + return new[] { await ReadFile(fileInfo) }.ToArray(); } - public async Task> GetProjectsInDir(string dirPath) + public async Task> GetProjectsInDir(string dirPath) { var dir = _fileSystem.DirectoryInfo.FromDirectoryName(dirPath); var projectFiles = await FindProjectsInDir(dir, SearchOption.TopDirectoryOnly); @@ -40,16 +40,16 @@ public async Task> GetProjectsInDir(string dirPath) : await FindProjectsInDir(dir, SearchOption.AllDirectories); } - public async Task> FindProjectsInDir(IDirectoryInfo dir, SearchOption searchMode) + public async Task> FindProjectsInDir(IDirectoryInfo dir, SearchOption searchMode) => await Task.WhenAll(FindProjects(dir, searchMode)); - private IEnumerable> FindProjects(IDirectoryInfo dir, SearchOption searchMode) => + private IReadOnlyCollection> FindProjects(IDirectoryInfo dir, SearchOption searchMode) => dir.EnumerateFiles("*.csproj", searchMode) .Union(dir.EnumerateFiles("Directory.build.props", searchMode)) .Union(dir.EnumerateFiles("Directory.build.targets", searchMode)) .Union(dir.EnumerateFiles("packages.config", searchMode)) .Select(ReadFile) - .ToList(); + .ToArray(); private static bool IsCsProjFile(IFileSystemInfo fileInfo) => fileInfo.Extension == ".csproj"; private static bool IsDirectoryBuildPropsFile(IFileSystemInfo fileInfo) => fileInfo.Name == "Directory.Build.props"; @@ -73,18 +73,16 @@ private async Task ReadFile(IFileSystemInfo fileInfo) throw new NotImplementedException("Unknown file type"); } - public async Task> Update(IDictionary> allResults) + public async Task> Update(SolutionResult result) { var updated = new List(); - foreach (var result in allResults) + foreach (var project in result.Details) { - var projectFile = result.Key; - var results = result.Value; - var update = projectFile.Update(results); + var update = project.ProjectFile.Update(project.Details); - var stream = _fileSystem.FileStream.Create(projectFile.FileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); + var stream = _fileSystem.FileStream.Create(project.ProjectFile.FileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); await new StreamWriter(stream).WriteAsync(update); - updated.Add(projectFile.FileName); + updated.Add(project.ProjectFile.FileName); } return updated; diff --git a/src/LibYear.Core/ProjectResult.cs b/src/LibYear.Core/ProjectResult.cs new file mode 100644 index 0000000..0afd0fd --- /dev/null +++ b/src/LibYear.Core/ProjectResult.cs @@ -0,0 +1,18 @@ +using LibYear.Core.FileTypes; + +namespace LibYear.Core; + +public class ProjectResult : HasAgeMeasurements +{ + public IProjectFile ProjectFile { get; } + public IReadOnlyCollection Details { get; } + + public ProjectResult(IProjectFile projectFile, IReadOnlyCollection details) + { + ProjectFile = projectFile; + Details = details; + } + + public override double DaysBehind + => Details.Sum(r => r.DaysBehind); +} \ No newline at end of file diff --git a/src/LibYear.Core/Result.cs b/src/LibYear.Core/Result.cs index 1d7dde9..52e5448 100644 --- a/src/LibYear.Core/Result.cs +++ b/src/LibYear.Core/Result.cs @@ -1,6 +1,6 @@ namespace LibYear.Core; -public class Result +public class Result : HasAgeMeasurements { public string Name { get; } public Release? Installed { get; } @@ -13,9 +13,6 @@ public Result(string name, Release? installed, Release? latest) Latest = latest; } - public double YearsBehind => DaysBehind > 0 - ? DaysBehind / 365 - : 0; - - private double DaysBehind => (Latest?.Date - Installed?.Date ?? TimeSpan.Zero).TotalDays; + public override double DaysBehind + => (Latest?.Date - Installed?.Date ?? TimeSpan.Zero).TotalDays; } \ No newline at end of file diff --git a/src/LibYear.Core/SolutionResult.cs b/src/LibYear.Core/SolutionResult.cs new file mode 100644 index 0000000..cc5bcfb --- /dev/null +++ b/src/LibYear.Core/SolutionResult.cs @@ -0,0 +1,12 @@ +namespace LibYear.Core; + +public class SolutionResult : HasAgeMeasurements +{ + public IReadOnlyCollection Details { get; } + + public SolutionResult(IReadOnlyCollection details) + => Details = details; + + public override double DaysBehind + => Details.Sum(r => r.DaysBehind); +} \ No newline at end of file diff --git a/src/LibYear/App.cs b/src/LibYear/App.cs index 0b92511..39b340c 100644 --- a/src/LibYear/App.cs +++ b/src/LibYear/App.cs @@ -1,5 +1,4 @@ using LibYear.Core; -using LibYear.Core.FileTypes; using Spectre.Console; namespace LibYear; @@ -17,61 +16,64 @@ public App(IPackageVersionChecker checker, IProjectFileManager projectFileManage _console = console; } - public async Task Run(Settings settings) + public async Task Run(Settings settings) { _console.WriteLine(); var projects = await _projectFileManager.GetAllProjects(settings.Paths); if (!projects.Any()) { _console.WriteLine("No project files found"); - return; + return 1; } - var allResults = await _checker.GetPackages(projects); - GetAllResultsTables(allResults, settings.QuietMode); + var result = await _checker.GetPackages(projects); + DisplayAllResultsTables(result, settings.QuietMode); if (settings.Update) { - var updated = await _projectFileManager.Update(allResults); + var updated = await _projectFileManager.Update(result); foreach (var projectFile in updated) { _console.WriteLine($"{projectFile} updated"); } } + + var limitChecker = new LimitChecker(settings); + return limitChecker.AnyLimitsExceeded(result) + ? 1 + : 0; } - private void GetAllResultsTables(IDictionary> allResults, bool quietMode) + private void DisplayAllResultsTables(SolutionResult allResults, bool quietMode) { - if (!allResults.Any()) + if (!allResults.Details.Any()) return; - int MaxLength(Func field) => allResults.Max(results => results.Value.Any() ? results.Value.Max(field) : 0); + int MaxLength(Func field) => allResults.Details.Max(results => results.Details.Any() ? results.Details.Max(field) : 0); var namePad = Math.Max("Package".Length, MaxLength(r => r.Name.Length)); var installedPad = Math.Max("Installed".Length, MaxLength(r => r.Installed?.Version.ToString().Length ?? 0)); var latestPad = Math.Max("Latest".Length, MaxLength(r => r.Latest?.Version.ToString().Length ?? 0)); - var width = allResults.Max(r => r.Key.FileName.Length); - foreach (var results in allResults) + var width = allResults.Details.Max(r => r.ProjectFile.FileName.Length); + foreach (var results in allResults.Details) GetResultsTable(results, width, namePad, installedPad, latestPad, quietMode); - if (allResults.Count > 1) + if (allResults.Details.Count > 1) { - var allTotal = allResults.Sum(ar => ar.Value.Sum(r => r.YearsBehind)); - _console.WriteLine($"Total is {allTotal:F1} libyears behind"); + _console.WriteLine($"Total is {allResults.YearsBehind:F1} libyears behind"); } } - private void GetResultsTable(KeyValuePair> results, int titlePad, int namePad, int installedPad, int latestPad, bool quietMode) + private void GetResultsTable(ProjectResult results, int titlePad, int namePad, int installedPad, int latestPad, bool quietMode) { - if (!results.Value.Any()) + if (!results.Details.Any()) return; - var projectTotal = results.Value.Sum(r => r.YearsBehind); var width = Math.Max(titlePad + 2, namePad + installedPad + latestPad + 48) + 2; var table = new Table { - Title = new TableTitle($" {results.Key.FileName}".PadRight(width)), - Caption = new TableTitle(($" Project is {projectTotal:F1} libyears behind").PadRight(width)), + Title = new TableTitle($" {results.ProjectFile.FileName}".PadRight(width)), + Caption = new TableTitle(($" Project is {results.YearsBehind:F1} libyears behind").PadRight(width)), Width = width }; table.AddColumn(new TableColumn("Package").Width(namePad)); @@ -81,7 +83,7 @@ private void GetResultsTable(KeyValuePair> res table.AddColumn(new TableColumn("Released")); table.AddColumn(new TableColumn("Age (y)")); - foreach (var result in results.Value.Where(r => !quietMode || r.YearsBehind > 0)) + foreach (var result in results.Details.Where(r => !quietMode || r.YearsBehind > 0)) { table.AddRow( result.Name, @@ -93,7 +95,7 @@ private void GetResultsTable(KeyValuePair> res ); } - if (quietMode && projectTotal == 0) + if (quietMode && results.YearsBehind == 0) { table.ShowHeaders = false; } diff --git a/src/LibYear/Command.cs b/src/LibYear/Command.cs index d9fa861..3a12b83 100644 --- a/src/LibYear/Command.cs +++ b/src/LibYear/Command.cs @@ -15,8 +15,7 @@ public override async Task ExecuteAsync(CommandContext context, Settings se { try { - await _console.Status().StartAsync("Running...", Run(settings)); - return 0; + return await _console.Status().StartAsync("Running...", Run(settings)); } catch (Exception e) { @@ -25,6 +24,6 @@ public override async Task ExecuteAsync(CommandContext context, Settings se } } - private Func Run(Settings settings) + private Func> Run(Settings settings) => async _ => await Factory.App(_console).Run(settings); } \ No newline at end of file diff --git a/src/LibYear/LibYear.csproj b/src/LibYear/LibYear.csproj index c1ed53f..ee6c8a2 100644 --- a/src/LibYear/LibYear.csproj +++ b/src/LibYear/LibYear.csproj @@ -1,6 +1,6 @@ - 6.0.1 + 7.0.0 Exe true libyear diff --git a/src/LibYear/LimitChecker.cs b/src/LibYear/LimitChecker.cs new file mode 100644 index 0000000..4146f00 --- /dev/null +++ b/src/LibYear/LimitChecker.cs @@ -0,0 +1,25 @@ +using LibYear.Core; + +namespace LibYear; + +public class LimitChecker +{ + private readonly Settings _settings; + + public LimitChecker(Settings settings) + => _settings = settings; + + public bool AnyLimitsExceeded(SolutionResult result) + => TotalLimitExceeded(result) + || AnyProjectLimitExceeded(result) + || AnyDependencyLimitExceeded(result); + + private bool AnyDependencyLimitExceeded(SolutionResult result) + => _settings.LimitAny != null && result.Details.Any(r => r.Details.Any(p => p.YearsBehind > _settings.LimitAny)); + + private bool AnyProjectLimitExceeded(SolutionResult result) + => _settings.LimitProject != null && result.Details.Any(r => r.Details.Sum(p => p.YearsBehind) > _settings.LimitProject); + + private bool TotalLimitExceeded(SolutionResult allResults) + => _settings.LimitTotal != null && allResults.YearsBehind > _settings.LimitTotal; +} \ No newline at end of file diff --git a/src/LibYear/Settings.cs b/src/LibYear/Settings.cs index 863da5b..ce44ee8 100644 --- a/src/LibYear/Settings.cs +++ b/src/LibYear/Settings.cs @@ -16,4 +16,16 @@ public class Settings : CommandSettings [CommandOption("-q|--quiet")] [Description("only output outdated packages")] public bool QuietMode { get; set; } + + [CommandOption("-l|--limit")] + [Description("fails if total libyears behind is greater than this value")] + public double? LimitTotal { get; set; } + + [CommandOption("-p|--limit-project")] + [Description("fails if any project's total libyears behind is greater than this value")] + public double? LimitProject { get; set; } + + [CommandOption("-a|--limit-any")] + [Description("fails if any dependency is more libyears behind than this value")] + public double? LimitAny { get; set; } } \ No newline at end of file diff --git a/test/LibYear.Core.Tests/PackageVersionCheckerTests.cs b/test/LibYear.Core.Tests/PackageVersionCheckerTests.cs index 44faf30..ffe3cbc 100644 --- a/test/LibYear.Core.Tests/PackageVersionCheckerTests.cs +++ b/test/LibYear.Core.Tests/PackageVersionCheckerTests.cs @@ -14,8 +14,7 @@ public async Task CanGetVersionInfoFromMetadata() //arrange var metadata = PackageSearchMetadataBuilder.FromIdentity(new PackageIdentity("test", new PackageVersion(1, 2, 3))).Build(); var metadataResource = Substitute.For(); - metadataResource.GetMetadataAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new List { metadata }); + metadataResource.GetMetadataAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(new [] { metadata }); var checker = new PackageVersionChecker(metadataResource); @@ -37,7 +36,7 @@ public async Task CanGetResultFromCache() var v1 = new Release(new PackageVersion(1, 2, 3), new DateTime(2015, 1, 1)); var v2 = new Release(new PackageVersion(2, 3, 4), new DateTime(2016, 1, 1)); - var versionCache = new Dictionary> { { "test", new List { v1, v2 } } }; + var versionCache = new Dictionary> { { "test", new List { v1, v2 } } }; var checker = new PackageVersionChecker(metadataResource, versionCache); //act @@ -48,6 +47,31 @@ public async Task CanGetResultFromCache() Assert.Equal("2.3.4", latest); } + [Fact] + public async Task CanGetResultFromMetadata() + { + //arrange + var metadata1 = Substitute.For(); + metadata1.Identity.Returns(new PackageIdentity("test", new PackageVersion(1, 2, 3))); + metadata1.IsListed.Returns(true); + + var metadata2 = Substitute.For(); + metadata2.Identity.Returns(new PackageIdentity("test", new PackageVersion(2, 3, 4))); + metadata2.IsListed.Returns(true); + + var metadataResource = Substitute.For(); + metadataResource.GetMetadataAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(new [] { metadata1, metadata2 }); + + var checker = new PackageVersionChecker(metadataResource); + + //act + var result = await checker.GetResult("test", new PackageVersion(1, 2, 3)); + + //assert + var latest = result.Latest!.Version.ToString(); + Assert.Equal("2.3.4", latest); + } + [Fact] public async Task InstalledVersionEqualsLatestVersionWithWildcard() { @@ -59,7 +83,7 @@ public async Task InstalledVersionEqualsLatestVersionWithWildcard() var v1 = new Release(new PackageVersion(1, 2, 3), new DateTime(2015, 1, 1)); var v2 = new Release(new PackageVersion(2, 3, 4), new DateTime(2016, 1, 1)); - var versionCache = new Dictionary> { { "test", new List { v1, v2 } } }; + var versionCache = new Dictionary> { { "test", new List { v1, v2 } } }; var checker = new PackageVersionChecker(metadataResource, versionCache); //act @@ -83,7 +107,7 @@ public async Task LatestDoesNotIncludePrerelease() var v1 = new Release(new PackageVersion(1, 2, 3), new DateTime(2015, 1, 1)); var v2 = new Release(new PackageVersion("2.3.4-beta-1"), new DateTime(2016, 1, 1)); - var versionCache = new Dictionary> { { "test", new List { v1, v2 } } }; + var versionCache = new Dictionary> { { "test", new List { v1, v2 } } }; var checker = new PackageVersionChecker(metadataResource, versionCache); //act @@ -105,7 +129,7 @@ public async Task LatestDoesNotIncludeUnpublished() var v1 = new Release(new PackageVersion(1, 2, 3), new DateTime(2015, 1, 1)); var v2 = new Release(new PackageVersion(2, 3, 4), new DateTime(2016, 1, 1), false); - var versionCache = new Dictionary> { { "test", new List { v1, v2 } } }; + var versionCache = new Dictionary> { { "test", new List { v1, v2 } } }; var checker = new PackageVersionChecker(metadataResource, versionCache); //act @@ -127,7 +151,7 @@ public async Task GetPackagesGetsPackages() var v1 = new Release(new PackageVersion(1, 2, 3), new DateTime(2015, 1, 1)); var v2 = new Release(new PackageVersion(2, 3, 4), new DateTime(2016, 1, 1)); - var versionCache = new Dictionary> { { "test", new List { v1, v2 } } }; + var versionCache = new Dictionary> { { "test", new List { v1, v2 } } }; var checker = new PackageVersionChecker(metadataResource, versionCache); var project = new TestProjectFile("test", new Dictionary { { "test", new PackageVersion(1, 2, 3) } }); @@ -135,7 +159,7 @@ public async Task GetPackagesGetsPackages() var packages = await checker.GetPackages(new[] { project }); //assert - var latest = packages.First().Value.First().Latest!.Version.ToString(); + var latest = packages.Details.First().Details.First().Latest!.Version.ToString(); Assert.Equal("2.3.4", latest); } diff --git a/test/LibYear.Core.Tests/PackageVersionTests.cs b/test/LibYear.Core.Tests/PackageVersionTests.cs index 87c047c..86e3b89 100644 --- a/test/LibYear.Core.Tests/PackageVersionTests.cs +++ b/test/LibYear.Core.Tests/PackageVersionTests.cs @@ -36,4 +36,12 @@ public void CanParseWildcardString() Assert.Equal("0.0.0", version.ToString()); Assert.True(version.IsWildcard); } + + [Fact] + public void CanParseFormattedString() + { + var version = new PackageVersion("1.2.3"); + + Assert.Equal("1.2.3", version.ToString("N", null)); + } } \ No newline at end of file diff --git a/test/LibYear.Core.Tests/ProjectFileManagerTests.cs b/test/LibYear.Core.Tests/ProjectFileManagerTests.cs index f293291..8825b80 100644 --- a/test/LibYear.Core.Tests/ProjectFileManagerTests.cs +++ b/test/LibYear.Core.Tests/ProjectFileManagerTests.cs @@ -1,5 +1,4 @@ using System.IO.Abstractions; -using LibYear.Core.FileTypes; using Xunit; namespace LibYear.Core.Tests; @@ -115,7 +114,7 @@ public async Task GetAllProjectsGetsCurrentDirectoryByDefault() var fileManager = new ProjectFileManager(fileSystem); //act - var projects = await fileManager.GetAllProjects(new List()); + var projects = await fileManager.GetAllProjects(Array.Empty()); //assert Assert.Contains(projects, p => p.FileName.EndsWith("project.csproj")); @@ -130,15 +129,13 @@ public async Task CanUpdateProjectFiles() var fileManager = new ProjectFileManager(fileSystem); //act - var allResults = new Dictionary> + var allResults = new SolutionResult(new[] { + new ProjectResult(new TestProjectFile("test1"), new[] { - new TestProjectFile("test1"), new[] - { - new Result("test1", new Release(new PackageVersion(0, 1, 0), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)), - } - } - }; + new Result("test1", new Release(new PackageVersion(0, 1, 0), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)), + }) + }); var updated = await fileManager.Update(allResults); //assert diff --git a/test/LibYear.Core.Tests/ProjectResultTests.cs b/test/LibYear.Core.Tests/ProjectResultTests.cs new file mode 100644 index 0000000..631185d --- /dev/null +++ b/test/LibYear.Core.Tests/ProjectResultTests.cs @@ -0,0 +1,25 @@ +using Xunit; + +namespace LibYear.Core.Tests; + +public class ProjectResultTests +{ + [Fact] + public void DaysBehindIsTotalFromDependencies() + { + //arrange + var project = new TestProjectFile("test1", new Dictionary()); + var deps = new[] + { + new Result("dep1", new Release(new PackageVersion("1.2.3"), new DateTime(2022, 10, 1)), new Release(new PackageVersion("1.2.4"), new DateTime(2022, 10, 5))), + new Result("dep2", new Release(new PackageVersion("1.2.3"), new DateTime(2022, 10, 2)), new Release(new PackageVersion("1.2.4"), new DateTime(2022, 10, 5))) + }; + var result = new ProjectResult(project, deps); + + //act + var behind = result.DaysBehind; + + //assert + Assert.Equal(7, behind); + } +} \ No newline at end of file diff --git a/test/LibYear.Core.Tests/SolutionResultTests.cs b/test/LibYear.Core.Tests/SolutionResultTests.cs new file mode 100644 index 0000000..c2229ce --- /dev/null +++ b/test/LibYear.Core.Tests/SolutionResultTests.cs @@ -0,0 +1,34 @@ +using Xunit; + +namespace LibYear.Core.Tests; + +public class SolutionResultTests +{ + [Fact] + public void DaysBehindIsTotalFromProjects() + { + //arrange + var project1 = new TestProjectFile("test1", new Dictionary()); + var project2 = new TestProjectFile("test2", new Dictionary()); + var projects = new[] + { + new ProjectResult(project1, new[] + { + new Result("dep1", new Release(new PackageVersion("1.2.3"), new DateTime(2022, 10, 1)), new Release(new PackageVersion("1.2.4"), new DateTime(2022, 10, 5))), + new Result("dep2", new Release(new PackageVersion("1.2.3"), new DateTime(2022, 10, 2)), new Release(new PackageVersion("1.2.4"), new DateTime(2022, 10, 5))) + }), + new ProjectResult(project2, new[] + { + new Result("dep3", new Release(new PackageVersion("1.2.3"), new DateTime(2022, 10, 3)), new Release(new PackageVersion("1.2.4"), new DateTime(2022, 10, 5))), + new Result("dep4", new Release(new PackageVersion("1.2.3"), new DateTime(2022, 10, 4)), new Release(new PackageVersion("1.2.4"), new DateTime(2022, 10, 5))) + }) + }; + var result = new SolutionResult(projects); + + //act + var behind = result.DaysBehind; + + //assert + Assert.Equal(10, behind); + } +} \ No newline at end of file diff --git a/test/LibYear.Core.Tests/TestProjectFile.cs b/test/LibYear.Core.Tests/TestProjectFile.cs index 8ca81c6..41ad28a 100644 --- a/test/LibYear.Core.Tests/TestProjectFile.cs +++ b/test/LibYear.Core.Tests/TestProjectFile.cs @@ -13,6 +13,6 @@ public TestProjectFile(string fileName, IDictionary? pa Packages = packages ?? new Dictionary(); } - public string Update(IEnumerable results) + public string Update(IReadOnlyCollection results) => string.Empty; } \ No newline at end of file diff --git a/test/LibYear.Tests/AppTests.cs b/test/LibYear.Tests/AppTests.cs index 4999d83..092b375 100644 --- a/test/LibYear.Tests/AppTests.cs +++ b/test/LibYear.Tests/AppTests.cs @@ -14,10 +14,11 @@ public async Task UpdateFlagUpdates() { //arrange var checker = Substitute.For(); + checker.GetPackages(Arg.Any>()).Returns(new SolutionResult(Array.Empty())); var manager = Substitute.For(); - manager.GetAllProjects(Arg.Any>()).Returns(new IProjectFile[] { new TestProjectFile("test1") }); - manager.Update(Arg.Any>>()).Returns(new[] { "updated" }); + manager.GetAllProjects(Arg.Any>()).Returns(new IProjectFile[] { new TestProjectFile("test1") }); + manager.Update(Arg.Any()).Returns(new[] { "updated" }); var console = new TestConsole(); var app = new App(checker, manager, console); @@ -35,14 +36,13 @@ public async Task DisplaysPackagesByDefault() //arrange var checker = Substitute.For(); var projectFile = new TestProjectFile("test project"); - var results = new Dictionary> - { - { projectFile, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) } } - }; - checker.GetPackages(Arg.Any>()).Returns(results); + var result = new ProjectResult(projectFile, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) }); + + var results = new SolutionResult(new[] { result }); + checker.GetPackages(Arg.Any>()).Returns(results); var manager = Substitute.For(); - manager.GetAllProjects(Arg.Any>()).Returns(new IProjectFile[] { projectFile }); + manager.GetAllProjects(Arg.Any>()).Returns(new IProjectFile[] { projectFile }); var console = new TestConsole(); var app = new App(checker, manager, console); @@ -60,14 +60,13 @@ public async Task QuietModeIgnoresPackageAtNewestVersion() //arrange var checker = Substitute.For(); var projectFile = new TestProjectFile("test project"); - var results = new Dictionary> - { - { projectFile, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) } } - }; - checker.GetPackages(Arg.Any>()).Returns(results); + var result = new ProjectResult(projectFile, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) }); + + var results = new SolutionResult(new[] { result }); + checker.GetPackages(Arg.Any>()).Returns(results); var manager = Substitute.For(); - manager.GetAllProjects(Arg.Any>()).Returns(new IProjectFile[] { projectFile }); + manager.GetAllProjects(Arg.Any>()).Returns(new IProjectFile[] { projectFile }); var console = new TestConsole(); var app = new App(checker, manager, console); @@ -86,15 +85,15 @@ public async Task MultiplePackagesShowsGrandTotal() var checker = Substitute.For(); var projectFile1 = new TestProjectFile("test project 1"); var projectFile2 = new TestProjectFile("test project 2"); - var results = new Dictionary> + var results = new SolutionResult(new [] { - { projectFile1, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) } }, - { projectFile2, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) } } - }; - checker.GetPackages(Arg.Any>()).Returns(results); + new ProjectResult(projectFile1, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) }), + new ProjectResult(projectFile2, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) }) + }); + checker.GetPackages(Arg.Any>()).Returns(results); var manager = Substitute.For(); - manager.GetAllProjects(Arg.Any>()).Returns(new IProjectFile[] { projectFile1, projectFile2 }); + manager.GetAllProjects(Arg.Any>()).Returns(new IProjectFile[] { projectFile1, projectFile2 }); var console = new TestConsole(); var app = new App(checker, manager, console); @@ -113,15 +112,15 @@ public async Task EmptyResultsAreSkipped() var checker = Substitute.For(); var projectFile1 = new TestProjectFile("test project 1"); var projectFile2 = new TestProjectFile("test project 2"); - var results = new Dictionary> + var results = new SolutionResult(new [] { - { projectFile1, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) } }, - { projectFile2, new List() } - }; - checker.GetPackages(Arg.Any>()).Returns(results); + new ProjectResult(projectFile1, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) }), + new ProjectResult(projectFile2, new List()) + }); + checker.GetPackages(Arg.Any>()).Returns(results); var manager = Substitute.For(); - manager.GetAllProjects(Arg.Any>()).Returns(new IProjectFile[] { projectFile1, projectFile2 }); + manager.GetAllProjects(Arg.Any>()).Returns(new IProjectFile[] { projectFile1, projectFile2 }); var console = new TestConsole(); var app = new App(checker, manager, console); @@ -141,7 +140,7 @@ public async Task MissingProjectFilesShowsError() var checker = Substitute.For(); var manager = Substitute.For(); - manager.GetAllProjects(Arg.Any>()).Returns(new List()); + manager.GetAllProjects(Arg.Any>()).Returns(Array.Empty()); var console = new TestConsole(); var app = new App(checker, manager, console); diff --git a/test/LibYear.Tests/LimitCheckerTests.cs b/test/LibYear.Tests/LimitCheckerTests.cs new file mode 100644 index 0000000..eb03239 --- /dev/null +++ b/test/LibYear.Tests/LimitCheckerTests.cs @@ -0,0 +1,120 @@ +using LibYear.Core; +using LibYear.Core.Tests; +using Xunit; + +namespace LibYear.Tests; + +public class LimitCheckerTests +{ + private static readonly SolutionResult SolutionResult = new(new[] + { + new ProjectResult(new TestProjectFile("test1", new Dictionary()), new[] + { + new Result("dep1", new Release(new PackageVersion("1.2.3"), new DateTime(2018, 1, 1)), new Release(new PackageVersion("1.2.4"), new DateTime(2022, 1, 1))), + new Result("dep2", new Release(new PackageVersion("1.2.3"), new DateTime(2019, 1, 1)), new Release(new PackageVersion("1.2.4"), new DateTime(2022, 1, 1))) + }), + new ProjectResult(new TestProjectFile("test1", new Dictionary()), new[] + { + new Result("dep3", new Release(new PackageVersion("1.2.3"), new DateTime(2020, 1, 1)), new Release(new PackageVersion("1.2.4"), new DateTime(2022, 1, 1))), + new Result("dep4", new Release(new PackageVersion("1.2.3"), new DateTime(2021, 1, 1)), new Release(new PackageVersion("1.2.4"), new DateTime(2022, 1, 1))) + }) + }); + + [Fact] + public void PassesWhenNoLimitsSet() + { + //arrange + var settings = new Settings(); + var checker = new LimitChecker(settings); + + //act + var passed = !checker.AnyLimitsExceeded(SolutionResult); + + //assert + Assert.True(passed); + } + + [Fact] + public void PassesWhenUnderTotalLimit() + { + //arrange + var settings = new Settings { LimitTotal = 11 }; + var checker = new LimitChecker(settings); + + //act + var passed = !checker.AnyLimitsExceeded(SolutionResult); + + //assert + Assert.True(passed); + } + + [Fact] + public void FailsWhenOverTotalLimit() + { + //arrange + var settings = new Settings { LimitTotal = 9 }; + var checker = new LimitChecker(settings); + + //act + var passed = !checker.AnyLimitsExceeded(SolutionResult); + + //assert + Assert.False(passed); + } + + [Fact] + public void PassesWhenUnderProjectLimit() + { + //arrange + var settings = new Settings { LimitProject = 8 }; + var checker = new LimitChecker(settings); + + //act + var passed = !checker.AnyLimitsExceeded(SolutionResult); + + //assert + Assert.True(passed); + } + + [Fact] + public void FailsWhenOverProjectLimit() + { + //arrange + var settings = new Settings { LimitProject = 4 }; + var checker = new LimitChecker(settings); + + //act + var passed = !checker.AnyLimitsExceeded(SolutionResult); + + //assert + Assert.False(passed); + } + + [Fact] + public void PassesWhenUnderAnyLimit() + { + //arrange + var settings = new Settings { LimitAny = 4.5 }; + var checker = new LimitChecker(settings); + + //act + var passed = !checker.AnyLimitsExceeded(SolutionResult); + + //assert + Assert.True(passed); + } + + [Fact] + public void FailsWhenOverAnyLimit() + { + //arrange + var settings = new Settings { LimitAny = 0.5 }; + var checker = new LimitChecker(settings); + + //act + var passed = !checker.AnyLimitsExceeded(SolutionResult); + + //assert + Assert.False(passed); + } +} \ No newline at end of file