diff --git a/Source/GitReleaseManager.Cli/GitReleaseManager.Cli.csproj b/Source/GitReleaseManager.Cli/GitReleaseManager.Cli.csproj index ce295098..22897588 100644 --- a/Source/GitReleaseManager.Cli/GitReleaseManager.Cli.csproj +++ b/Source/GitReleaseManager.Cli/GitReleaseManager.Cli.csproj @@ -14,13 +14,14 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/Source/GitReleaseManager.Cli/Logging/LogConfiguration.cs b/Source/GitReleaseManager.Cli/Logging/LogConfiguration.cs index b3dd2966..64e358cb 100644 --- a/Source/GitReleaseManager.Cli/Logging/LogConfiguration.cs +++ b/Source/GitReleaseManager.Cli/Logging/LogConfiguration.cs @@ -9,7 +9,7 @@ namespace GitReleaseManager.Cli.Logging using System.Diagnostics; using System.Text; using Destructurama; - using GitReleaseManager.Cli.Options; + using GitReleaseManager.Core.Options; using Octokit; using Serilog; using Serilog.Events; diff --git a/Source/GitReleaseManager.Cli/Program.cs b/Source/GitReleaseManager.Cli/Program.cs index fb6de33f..922675a9 100644 --- a/Source/GitReleaseManager.Cli/Program.cs +++ b/Source/GitReleaseManager.Cli/Program.cs @@ -7,24 +7,25 @@ namespace GitReleaseManager.Cli { using System; - using System.IO; using System.Net; using System.Reflection; using System.Threading.Tasks; - using AutoMapper; using CommandLine; using GitReleaseManager.Cli.Logging; - using GitReleaseManager.Cli.Options; using GitReleaseManager.Core; + using GitReleaseManager.Core.Commands; using GitReleaseManager.Core.Configuration; using GitReleaseManager.Core.Helpers; + using GitReleaseManager.Core.Options; + using GitReleaseManager.Core.Provider; + using GitReleaseManager.Core.ReleaseNotes; + using Microsoft.Extensions.DependencyInjection; + using Octokit; using Serilog; public static class Program { - private static FileSystem _fileSystem; - private static IMapper _mapper; - private static IVcsProvider _vcsProvider; + private static IServiceProvider _serviceProvider; private static async Task Main(string[] args) { @@ -32,10 +33,6 @@ private static async Task Main(string[] args) // we've upgraded to latest Octokit. ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; - _fileSystem = new FileSystem(); - - _mapper = AutoMapperConfiguration.Configure(); - try { return await Parser.Default.ParseArguments(args) @@ -43,17 +40,18 @@ private static async Task Main(string[] args) .WithParsed(CreateFiglet) .WithParsed(LogOptions) .WithParsed(ReportUsernamePasswordDeprecation) + .WithParsed(RegisterServices) .MapResult( - (CreateSubOptions opts) => CreateReleaseAsync(opts), - (DiscardSubOptions opts) => DiscardReleaseAsync(opts), - (AddAssetSubOptions opts) => AddAssetsAsync(opts), - (CloseSubOptions opts) => CloseMilestoneAsync(opts), - (OpenSubOptions opts) => OpenMilestoneAsync(opts), - (PublishSubOptions opts) => PublishReleaseAsync(opts), - (ExportSubOptions opts) => ExportReleasesAsync(opts), - (InitSubOptions opts) => CreateSampleConfigFileAsync(opts), - (ShowConfigSubOptions opts) => ShowConfigAsync(opts), - (LabelSubOptions opts) => CreateLabelsAsync(opts), + (CreateSubOptions opts) => ExecuteCommand(opts), + (DiscardSubOptions opts) => ExecuteCommand(opts), + (AddAssetSubOptions opts) => ExecuteCommand(opts), + (CloseSubOptions opts) => ExecuteCommand(opts), + (OpenSubOptions opts) => ExecuteCommand(opts), + (PublishSubOptions opts) => ExecuteCommand(opts), + (ExportSubOptions opts) => ExecuteCommand(opts), + (InitSubOptions opts) => ExecuteCommand(opts), + (ShowConfigSubOptions opts) => ExecuteCommand(opts), + (LabelSubOptions opts) => ExecuteCommand(opts), errs => Task.FromResult(1)).ConfigureAwait(false); } catch (AggregateException ex) @@ -74,6 +72,53 @@ private static async Task Main(string[] args) finally { Log.CloseAndFlush(); + DisposeServices(); + } + } + + private static void RegisterServices(BaseVcsOptions options) + { + var fileSystem = new FileSystem(); + var logger = Log.ForContext(); + var mapper = AutoMapperConfiguration.Configure(); + var configuration = ConfigurationProvider.Provide(options.TargetDirectory ?? Environment.CurrentDirectory, fileSystem); + + var credentials = string.IsNullOrWhiteSpace(options.Token) + ? new Credentials(options.UserName, options.Password) + : new Credentials(options.Token); + + var gitHubClient = new GitHubClient(new ProductHeaderValue("GitReleaseManager")) { Credentials = credentials }; + + var serviceCollection = new ServiceCollection() + .AddSingleton(logger) + .AddSingleton(mapper) + .AddSingleton(configuration) + .AddSingleton(configuration.Export) + .AddSingleton, AddAssetsCommand>() + .AddSingleton, CloseCommand>() + .AddSingleton, CreateCommand>() + .AddSingleton, DiscardCommand>() + .AddSingleton, ExportCommand>() + .AddSingleton, InitCommand>() + .AddSingleton, LabelCommand>() + .AddSingleton, OpenCommand>() + .AddSingleton, PublishCommand>() + .AddSingleton, ShowConfigCommand>() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(gitHubClient) + .AddSingleton() + .AddSingleton(); + + _serviceProvider = serviceCollection.BuildServiceProvider(); + } + + private static void DisposeServices() + { + if (_serviceProvider is IDisposable serviceProvider) + { + serviceProvider.Dispose(); } } @@ -145,129 +190,11 @@ private static int GetConsoleWidth() } } - private static async Task CreateReleaseAsync(CreateSubOptions subOptions) - { - Log.Information("Creating release..."); - _vcsProvider = GetVcsProvider(subOptions); - - Core.Model.Release release; - if (!string.IsNullOrEmpty(subOptions.Milestone)) - { - Log.Verbose("Milestone {Milestone} was specified", subOptions.Milestone); - var releaseName = subOptions.Name; - if (string.IsNullOrWhiteSpace(releaseName)) - { - Log.Verbose("No Release Name was specified, using {Milestone}.", subOptions.Milestone); - releaseName = subOptions.Milestone; - } - - release = await _vcsProvider.CreateReleaseFromMilestone(subOptions.RepositoryOwner, subOptions.RepositoryName, subOptions.Milestone, releaseName, subOptions.TargetCommitish, subOptions.AssetPaths, subOptions.Prerelease).ConfigureAwait(false); - } - else - { - Log.Verbose("No milestone was specified, switching to release creating from input file"); - release = await _vcsProvider.CreateReleaseFromInputFile(subOptions.RepositoryOwner, subOptions.RepositoryName, subOptions.Name, subOptions.InputFilePath, subOptions.TargetCommitish, subOptions.AssetPaths, subOptions.Prerelease).ConfigureAwait(false); - } - - Log.Information("Drafted release is available at:\n{HtmlUrl}", release.HtmlUrl); - Log.Verbose("Body:\n{Body}", release.Body); - return 0; - } - - private static async Task DiscardReleaseAsync(DiscardSubOptions subOptions) + private static Task ExecuteCommand(TOptions options) + where TOptions : BaseSubOptions { - Log.Information("Discarding release {Milestone}", subOptions.Milestone); - _vcsProvider = GetVcsProvider(subOptions); - - await _vcsProvider.DiscardRelease(subOptions.RepositoryOwner, subOptions.RepositoryName, subOptions.Milestone); - - return 0; - } - - private static async Task AddAssetsAsync(AddAssetSubOptions subOptions) - { - Log.Information("Uploading assets"); - _vcsProvider = GetVcsProvider(subOptions); - - await _vcsProvider.AddAssets(subOptions.RepositoryOwner, subOptions.RepositoryName, subOptions.TagName, subOptions.AssetPaths).ConfigureAwait(false); - - return 0; - } - - private static async Task CloseMilestoneAsync(CloseSubOptions subOptions) - { - Log.Information("Closing milestone {Milestone}", subOptions.Milestone); - _vcsProvider = GetVcsProvider(subOptions); - - await _vcsProvider.CloseMilestone(subOptions.RepositoryOwner, subOptions.RepositoryName, subOptions.Milestone).ConfigureAwait(false); - - return 0; - } - - private static async Task OpenMilestoneAsync(OpenSubOptions subOptions) - { - Log.Information("Opening milestone {Milestone}", subOptions.Milestone); - _vcsProvider = GetVcsProvider(subOptions); - - await _vcsProvider.OpenMilestone(subOptions.RepositoryOwner, subOptions.RepositoryName, subOptions.Milestone).ConfigureAwait(false); - - return 0; - } - - private static async Task PublishReleaseAsync(PublishSubOptions subOptions) - { - _vcsProvider = GetVcsProvider(subOptions); - - await _vcsProvider.PublishRelease(subOptions.RepositoryOwner, subOptions.RepositoryName, subOptions.TagName).ConfigureAwait(false); - return 0; - } - - private static async Task ExportReleasesAsync(ExportSubOptions subOptions) - { - Log.Information("Exporting release {TagName}", subOptions.TagName); - _vcsProvider = GetVcsProvider(subOptions); - - var releasesMarkdown = await _vcsProvider.ExportReleases(subOptions.RepositoryOwner, subOptions.RepositoryName, subOptions.TagName).ConfigureAwait(false); - - using (var sw = new StreamWriter(File.Open(subOptions.FileOutputPath, FileMode.OpenOrCreate))) - { - sw.Write(releasesMarkdown); - } - - return 0; - } - - private static Task CreateSampleConfigFileAsync(InitSubOptions subOptions) - { - Log.Information("Creating sample configuration file"); - var directory = subOptions.TargetDirectory ?? Environment.CurrentDirectory; - ConfigurationProvider.WriteSample(directory, _fileSystem); - return Task.FromResult(0); - } - - private static Task ShowConfigAsync(ShowConfigSubOptions subOptions) - { - var configuration = ConfigurationProvider.GetEffectiveConfigAsString(subOptions.TargetDirectory ?? Environment.CurrentDirectory, _fileSystem); - - Log.Information("{Configuration}", configuration); - return Task.FromResult(0); - } - - private static async Task CreateLabelsAsync(LabelSubOptions subOptions) - { - Log.Information("Creating standard labels"); - _vcsProvider = GetVcsProvider(subOptions); - - await _vcsProvider.CreateLabels(subOptions.RepositoryOwner, subOptions.RepositoryName).ConfigureAwait(false); - return 0; - } - - private static IVcsProvider GetVcsProvider(BaseVcsOptions subOptions) - { - var configuration = ConfigurationProvider.Provide(subOptions.TargetDirectory ?? Environment.CurrentDirectory, _fileSystem); - - Log.Information("Using {Provider} as VCS Provider", "GitHub"); - return new GitHubProvider(_mapper, configuration, subOptions.UserName, subOptions.Password, subOptions.Token); + var command = _serviceProvider.GetRequiredService>(); + return command.Execute(options); } private static void LogOptions(BaseSubOptions options) diff --git a/Source/GitReleaseManager.Core.Tests/Commands/AddAssetsCommandTests.cs b/Source/GitReleaseManager.Core.Tests/Commands/AddAssetsCommandTests.cs new file mode 100644 index 00000000..f3b0cc62 --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/Commands/AddAssetsCommandTests.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading.Tasks; +using GitReleaseManager.Core.Commands; +using GitReleaseManager.Core.Options; +using NSubstitute; +using NUnit.Framework; +using Serilog; +using Shouldly; + +namespace GitReleaseManager.Core.Tests.Commands +{ + [TestFixture] + public class AddAssetsCommandTests + { + private IVcsService _vcsService; + private ILogger _logger; + private AddAssetsCommand _command; + + [SetUp] + public void Setup() + { + _vcsService = Substitute.For(); + _logger = Substitute.For(); + _command = new AddAssetsCommand(_vcsService, _logger); + } + + [Test] + public async Task Should_Execute_Command() + { + var options = new AddAssetSubOptions + { + RepositoryOwner = "owner", + RepositoryName = "repository", + TagName = "0.1.0", + AssetPaths = new List(), + }; + + _vcsService.AddAssetsAsync(options.RepositoryOwner, options.RepositoryName, options.TagName, options.AssetPaths). + Returns(Task.CompletedTask); + + var result = await _command.Execute(options).ConfigureAwait(false); + result.ShouldBe(0); + + await _vcsService.Received(1).AddAssetsAsync(options.RepositoryOwner, options.RepositoryName, options.TagName, options.AssetPaths).ConfigureAwait(false); + _logger.Received(1).Information(Arg.Any()); + } + } +} \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/Commands/CloseCommandTests.cs b/Source/GitReleaseManager.Core.Tests/Commands/CloseCommandTests.cs new file mode 100644 index 00000000..d8a28f44 --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/Commands/CloseCommandTests.cs @@ -0,0 +1,52 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System.Threading.Tasks; +using GitReleaseManager.Core.Commands; +using GitReleaseManager.Core.Options; +using NSubstitute; +using NUnit.Framework; +using Serilog; +using Shouldly; + +namespace GitReleaseManager.Core.Tests.Commands +{ + [TestFixture] + public class CloseCommandTests + { + private IVcsService _vcsService; + private ILogger _logger; + private CloseCommand _command; + + [SetUp] + public void Setup() + { + _vcsService = Substitute.For(); + _logger = Substitute.For(); + _command = new CloseCommand(_vcsService, _logger); + } + + [Test] + public async Task Should_Execute_Command() + { + var options = new CloseSubOptions + { + RepositoryOwner = "owner", + RepositoryName = "repository", + Milestone = "0.1.0", + }; + + _vcsService.CloseMilestoneAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone) + .Returns(Task.CompletedTask); + + var result = await _command.Execute(options).ConfigureAwait(false); + result.ShouldBe(0); + + await _vcsService.Received(1).CloseMilestoneAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone).ConfigureAwait(false); + _logger.Received(1).Information(Arg.Any(), options.Milestone); + } + } +} \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/Commands/CreateCommandTests.cs b/Source/GitReleaseManager.Core.Tests/Commands/CreateCommandTests.cs new file mode 100644 index 00000000..414a2a8d --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/Commands/CreateCommandTests.cs @@ -0,0 +1,91 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading.Tasks; +using GitReleaseManager.Core.Commands; +using GitReleaseManager.Core.Model; +using GitReleaseManager.Core.Options; +using NSubstitute; +using NUnit.Framework; +using Serilog; +using Shouldly; + +namespace GitReleaseManager.Core.Tests.Commands +{ + [TestFixture] + public class CreateCommandTests + { + private readonly Release _release = new Release { Body = "Release Body", HtmlUrl = "Html Url" }; + + private IVcsService _vcsService; + private ILogger _logger; + private CreateCommand _command; + + [SetUp] + public void Setup() + { + _vcsService = Substitute.For(); + _logger = Substitute.For(); + _command = new CreateCommand(_vcsService, _logger); + } + + [TestCase(null, 2)] + [TestCase("release", 1)] + public async Task Should_Create_Release_From_Milestone(string name, int logVerboseCount) + { + var options = new CreateSubOptions + { + RepositoryOwner = "owner", + RepositoryName = "repository", + Milestone = "milestone", + Name = name, + TargetCommitish = "target commitish", + AssetPaths = new List(), + Prerelease = false, + }; + + var releaseName = options.Name ?? options.Milestone; + + _vcsService.CreateReleaseFromMilestoneAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone, releaseName, options.TargetCommitish, options.AssetPaths, options.Prerelease) + .Returns(_release); + + var result = await _command.Execute(options).ConfigureAwait(false); + result.ShouldBe(0); + + await _vcsService.Received(1).CreateReleaseFromMilestoneAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone, releaseName, options.TargetCommitish, options.AssetPaths, options.Prerelease).ConfigureAwait(false); + _logger.Received(1).Information(Arg.Any()); + _logger.Received(logVerboseCount).Verbose(Arg.Any(), options.Milestone); + _logger.Received(1).Information(Arg.Any(), _release.HtmlUrl); + _logger.Received(1).Verbose(Arg.Any(), _release.Body); + } + + [Test] + public async Task Should_Create_Release_From_InputFile() + { + var options = new CreateSubOptions + { + RepositoryOwner = "owner", + RepositoryName = "repository", + InputFilePath = "file path", + TargetCommitish = "target commitish", + AssetPaths = new List(), + Prerelease = false, + }; + + _vcsService.CreateReleaseFromInputFileAsync(options.RepositoryOwner, options.RepositoryName, options.Name, options.InputFilePath, options.TargetCommitish, options.AssetPaths, options.Prerelease) + .Returns(_release); + + var result = await _command.Execute(options).ConfigureAwait(false); + result.ShouldBe(0); + + await _vcsService.Received(1).CreateReleaseFromInputFileAsync(options.RepositoryOwner, options.RepositoryName, options.Name, options.InputFilePath, options.TargetCommitish, options.AssetPaths, options.Prerelease).ConfigureAwait(false); + _logger.Received(1).Information(Arg.Any()); + _logger.Received(1).Information(Arg.Any(), _release.HtmlUrl); + _logger.Received(1).Verbose(Arg.Any(), _release.Body); + } + } +} \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/Commands/DiscardCommandTests.cs b/Source/GitReleaseManager.Core.Tests/Commands/DiscardCommandTests.cs new file mode 100644 index 00000000..c0e0aa88 --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/Commands/DiscardCommandTests.cs @@ -0,0 +1,52 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System.Threading.Tasks; +using GitReleaseManager.Core.Commands; +using GitReleaseManager.Core.Options; +using NSubstitute; +using NUnit.Framework; +using Serilog; +using Shouldly; + +namespace GitReleaseManager.Core.Tests.Commands +{ + [TestFixture] + public class DiscardCommandTests + { + private IVcsService _vcsService; + private ILogger _logger; + private DiscardCommand _command; + + [SetUp] + public void Setup() + { + _vcsService = Substitute.For(); + _logger = Substitute.For(); + _command = new DiscardCommand(_vcsService, _logger); + } + + [Test] + public async Task Should_Execute_Command() + { + var options = new DiscardSubOptions + { + RepositoryOwner = "owner", + RepositoryName = "repository", + Milestone = "0.1.0", + }; + + _vcsService.DiscardReleaseAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone) + .Returns(Task.CompletedTask); + + var result = await _command.Execute(options).ConfigureAwait(false); + result.ShouldBe(0); + + await _vcsService.Received(1).DiscardReleaseAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone).ConfigureAwait(false); + _logger.Received(1).Information(Arg.Any(), options.Milestone); + } + } +} \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/Commands/ExportCommandTests.cs b/Source/GitReleaseManager.Core.Tests/Commands/ExportCommandTests.cs new file mode 100644 index 00000000..fc8988b3 --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/Commands/ExportCommandTests.cs @@ -0,0 +1,69 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System.IO; +using System.Threading.Tasks; +using GitReleaseManager.Core.Commands; +using GitReleaseManager.Core.Options; +using NSubstitute; +using NUnit.Framework; +using Serilog; +using Shouldly; + +namespace GitReleaseManager.Core.Tests.Commands +{ + [TestFixture] + public class ExportCommandTests + { + private IVcsService _vcsService; + private ILogger _logger; + private ExportCommand _command; + private string _fileOutputPath; + + [SetUp] + public void Setup() + { + _vcsService = Substitute.For(); + _logger = Substitute.For(); + _command = new ExportCommand(_vcsService, _logger); + _fileOutputPath = Path.Combine(Path.GetTempPath(), "ReleaseExport.txt"); + } + + [Test] + public async Task Should_Execute_Command() + { + var options = new ExportSubOptions + { + RepositoryOwner = "owner", + RepositoryName = "repository", + TagName = "0.1.0", + FileOutputPath = _fileOutputPath, + }; + + var releaseText = "releaseText"; + + _vcsService.ExportReleasesAsync(options.RepositoryOwner, options.RepositoryName, options.TagName) + .Returns(releaseText); + + var result = await _command.Execute(options).ConfigureAwait(false); + result.ShouldBe(0); + + var exportFileExists = File.Exists(_fileOutputPath); + exportFileExists.ShouldBeTrue(); + + var exportFileContent = File.ReadAllText(_fileOutputPath); + exportFileContent.ShouldBe(releaseText); + + await _vcsService.Received(1).ExportReleasesAsync(options.RepositoryOwner, options.RepositoryName, options.TagName).ConfigureAwait(false); + _logger.Received(1).Information(Arg.Any(), options.TagName); + + if (exportFileExists) + { + File.Delete(_fileOutputPath); + } + } + } +} \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/Commands/InitCommandTests.cs b/Source/GitReleaseManager.Core.Tests/Commands/InitCommandTests.cs new file mode 100644 index 00000000..7cf01494 --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/Commands/InitCommandTests.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System.IO; +using System.Threading.Tasks; +using GitReleaseManager.Core.Commands; +using GitReleaseManager.Core.Helpers; +using GitReleaseManager.Core.Options; +using NSubstitute; +using NUnit.Framework; +using Serilog; +using Shouldly; + +namespace GitReleaseManager.Core.Tests.Commands +{ + [TestFixture] + public class InitCommandTests + { + private IFileSystem _fileSystem; + private ILogger _logger; + private InitCommand _command; + private string _targetDirectory; + + [SetUp] + public void Setup() + { + _fileSystem = new FileSystem(); + _logger = Substitute.For(); + _command = new InitCommand(_fileSystem, _logger); + _targetDirectory = Path.GetTempPath(); + } + + [Test] + public async Task Should_Execute_Command() + { + var options = new InitSubOptions { TargetDirectory = _targetDirectory }; + + var result = await _command.Execute(options).ConfigureAwait(false); + result.ShouldBe(0); + + var configFilePath = Path.Combine(_targetDirectory, "GitReleaseManager.yaml"); + var configFileExists = File.Exists(configFilePath); + configFileExists.ShouldBeTrue(); + + _logger.Received(1).Information(Arg.Any()); + + if (configFileExists) + { + File.Delete(configFilePath); + } + } + } +} \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/Commands/LabelCommandTests.cs b/Source/GitReleaseManager.Core.Tests/Commands/LabelCommandTests.cs new file mode 100644 index 00000000..f1f13a4a --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/Commands/LabelCommandTests.cs @@ -0,0 +1,51 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System.Threading.Tasks; +using GitReleaseManager.Core.Commands; +using GitReleaseManager.Core.Options; +using NSubstitute; +using NUnit.Framework; +using Serilog; +using Shouldly; + +namespace GitReleaseManager.Core.Tests.Commands +{ + [TestFixture] + public class LabelCommandTests + { + private IVcsService _vcsService; + private ILogger _logger; + private LabelCommand _command; + + [SetUp] + public void Setup() + { + _vcsService = Substitute.For(); + _logger = Substitute.For(); + _command = new LabelCommand(_vcsService, _logger); + } + + [Test] + public async Task Should_Execute_Command() + { + var options = new LabelSubOptions + { + RepositoryOwner = "owner", + RepositoryName = "repository", + }; + + _vcsService.CreateLabelsAsync(options.RepositoryOwner, options.RepositoryName) + .Returns(Task.CompletedTask); + + var result = await _command.Execute(options).ConfigureAwait(false); + result.ShouldBe(0); + + await _vcsService.Received(1).CreateLabelsAsync(options.RepositoryOwner, options.RepositoryName).ConfigureAwait(false); + _logger.Received(1).Information(Arg.Any()); + } + } +} \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/Commands/OpenCommandTests.cs b/Source/GitReleaseManager.Core.Tests/Commands/OpenCommandTests.cs new file mode 100644 index 00000000..c9b660c7 --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/Commands/OpenCommandTests.cs @@ -0,0 +1,52 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System.Threading.Tasks; +using GitReleaseManager.Core.Commands; +using GitReleaseManager.Core.Options; +using NSubstitute; +using NUnit.Framework; +using Serilog; +using Shouldly; + +namespace GitReleaseManager.Core.Tests.Commands +{ + [TestFixture] + public class OpenCommandTests + { + private IVcsService _vcsService; + private ILogger _logger; + private OpenCommand _command; + + [SetUp] + public void Setup() + { + _vcsService = Substitute.For(); + _logger = Substitute.For(); + _command = new OpenCommand(_vcsService, _logger); + } + + [Test] + public async Task Should_Execute_Command() + { + var options = new OpenSubOptions + { + RepositoryOwner = "owner", + RepositoryName = "repository", + Milestone = "0.1.0", + }; + + _vcsService.OpenMilestoneAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone) + .Returns(Task.CompletedTask); + + var result = await _command.Execute(options).ConfigureAwait(false); + result.ShouldBe(0); + + await _vcsService.Received(1).OpenMilestoneAsync(options.RepositoryOwner, options.RepositoryName, options.Milestone).ConfigureAwait(false); + _logger.Received(1).Information(Arg.Any(), options.Milestone); + } + } +} \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/Commands/PublishCommandTests.cs b/Source/GitReleaseManager.Core.Tests/Commands/PublishCommandTests.cs new file mode 100644 index 00000000..5a585a36 --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/Commands/PublishCommandTests.cs @@ -0,0 +1,52 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System.Threading.Tasks; +using GitReleaseManager.Core.Commands; +using GitReleaseManager.Core.Options; +using NSubstitute; +using NUnit.Framework; +using Serilog; +using Shouldly; + +namespace GitReleaseManager.Core.Tests.Commands +{ + [TestFixture] + public class PublishCommandTests + { + private IVcsService _vcsService; + private ILogger _logger; + private PublishCommand _command; + + [SetUp] + public void Setup() + { + _vcsService = Substitute.For(); + _logger = Substitute.For(); + _command = new PublishCommand(_vcsService, _logger); + } + + [Test] + public async Task Should_Execute_Command() + { + var options = new PublishSubOptions + { + RepositoryOwner = "owner", + RepositoryName = "repository", + TagName = "0.1.0", + }; + + _vcsService.PublishReleaseAsync(options.RepositoryOwner, options.RepositoryName, options.TagName) + .Returns(Task.CompletedTask); + + var result = await _command.Execute(options).ConfigureAwait(false); + result.ShouldBe(0); + + await _vcsService.Received(1).PublishReleaseAsync(options.RepositoryOwner, options.RepositoryName, options.TagName).ConfigureAwait(false); + _logger.Received(1).Information(Arg.Any(), options.TagName); + } + } +} \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/Commands/ShowConfigCommandTests.cs b/Source/GitReleaseManager.Core.Tests/Commands/ShowConfigCommandTests.cs new file mode 100644 index 00000000..dd9bc627 --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/Commands/ShowConfigCommandTests.cs @@ -0,0 +1,44 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System.Threading.Tasks; +using GitReleaseManager.Core.Commands; +using GitReleaseManager.Core.Helpers; +using GitReleaseManager.Core.Options; +using NSubstitute; +using NUnit.Framework; +using Serilog; +using Shouldly; + +namespace GitReleaseManager.Core.Tests.Commands +{ + [TestFixture] + public class ShowConfigCommandTests + { + private IFileSystem _fileSystem; + private ILogger _logger; + private ShowConfigCommand _command; + + [SetUp] + public void Setup() + { + _fileSystem = Substitute.For(); + _logger = Substitute.For(); + _command = new ShowConfigCommand(_fileSystem, _logger); + } + + [Test] + public async Task Should_Execute_Command() + { + var options = new ShowConfigSubOptions(); + + var result = await _command.Execute(options).ConfigureAwait(false); + result.ShouldBe(0); + + _logger.Received(1).Information(Arg.Any(), Arg.Any()); + } + } +} \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/EnsureTests.cs b/Source/GitReleaseManager.Core.Tests/EnsureTests.cs new file mode 100644 index 00000000..87963e90 --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/EnsureTests.cs @@ -0,0 +1,51 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System; +using System.IO; +using NUnit.Framework; +using Shouldly; + +namespace GitReleaseManager.Core.Tests +{ + [TestFixture] + public class EnsureTests + { + [Test] + public void Should_Throw_Exception_When_String_Is_Null() + { + var paramName = "str"; + + var ex = Should.Throw(() => Ensure.IsNotNullOrWhiteSpace(null, paramName)); + ex.ParamName.ShouldBe(paramName); + } + + [TestCase("")] + [TestCase(" ")] + public void Should_Throw_Exception_When_String_Is_Whitespace(string str) + { + var paramName = nameof(str); + + var ex = Should.Throw(() => Ensure.IsNotNullOrWhiteSpace(str, paramName)); + ex.Message.ShouldContain("Value cannot be empty or white-space."); + ex.ParamName.ShouldBe(paramName); + } + + [Test] + public void Should_Throw_Exception_When_File_Not_Exists() + { + var tempPath = Path.GetTempPath(); + var tempFile = "TempFile.txt"; + + var path = Path.Combine(tempPath, tempFile); + var message = "File does not exist"; + + var ex = Should.Throw(() => Ensure.FileExists(path, message)); + ex.Message.ShouldBe(message); + ex.FileName.ShouldBe(tempFile); + } + } +} \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/GitReleaseManager.Core.Tests.csproj b/Source/GitReleaseManager.Core.Tests/GitReleaseManager.Core.Tests.csproj new file mode 100644 index 00000000..6e29a42c --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/GitReleaseManager.Core.Tests.csproj @@ -0,0 +1,29 @@ + + + 8.0 + net472;netcoreapp2.2 + GitReleaseManager.Core.Tests + Test Project for GitReleaseManager.Core + $(NoWarn);CA1707 + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + \ No newline at end of file diff --git a/Source/GitReleaseManager.Core.Tests/Provider/GitHubProviderTests.cs b/Source/GitReleaseManager.Core.Tests/Provider/GitHubProviderTests.cs new file mode 100644 index 00000000..fb7cb093 --- /dev/null +++ b/Source/GitReleaseManager.Core.Tests/Provider/GitHubProviderTests.cs @@ -0,0 +1,813 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) 2015 - Present - GitTools Contributors +// +// ----------------------------------------------------------------------- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using AutoMapper; +using GitReleaseManager.Core.Provider; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using NUnit.Framework; +using Octokit; +using Shouldly; +using ApiException = GitReleaseManager.Core.Exceptions.ApiException; +using Issue = GitReleaseManager.Core.Model.Issue; +using IssueComment = GitReleaseManager.Core.Model.IssueComment; +using ItemState = GitReleaseManager.Core.Model.ItemState; +using ItemStateFilter = GitReleaseManager.Core.Model.ItemStateFilter; +using Label = GitReleaseManager.Core.Model.Label; +using Milestone = GitReleaseManager.Core.Model.Milestone; +using NotFoundException = GitReleaseManager.Core.Exceptions.NotFoundException; +using RateLimit = GitReleaseManager.Core.Model.RateLimit; +using Release = GitReleaseManager.Core.Model.Release; +using ReleaseAssetUpload = GitReleaseManager.Core.Model.ReleaseAssetUpload; + +namespace GitReleaseManager.Core.Tests.Provider +{ + [TestFixture] + public class GitHubProviderTests + { + private const string _owner = "owner"; + private const string _repository = "repository"; + private const string _base = "0.1.0"; + private const string _head = "0.5.0"; + private const int _milestoneNumber = 1; + private const string _milestoneNumberString = "1"; + private const string _milestoneTitle = "0.1.0"; + private const int _issueNumber = 1; + private const string _issueComment = "Issue Comment"; + private const string _labelName = "Label"; + private const string _tagName = "0.1.0"; + private const int _releaseId = 1; + private const int _assetId = 1; + private const string _notFoundMessage = "NotFound"; + + private readonly Release _release = new Release(); + private readonly ReleaseAssetUpload _releaseAssetUpload = new ReleaseAssetUpload(); + private readonly Octokit.NewLabel _newLabel = new Octokit.NewLabel(_labelName, "ffffff"); + private readonly Octokit.NewRelease _newRelease = new Octokit.NewRelease(_tagName); + private readonly Exception _exception = new Exception("API Error"); + private readonly Octokit.NotFoundException _notFoundException = new Octokit.NotFoundException(_notFoundMessage, HttpStatusCode.NotFound); + + private IMapper _mapper; + private IGitHubClient _gitHubClient; + private GitHubProvider _gitHubProvider; + + [SetUp] + public void Setup() + { + _mapper = Substitute.For(); + _gitHubClient = Substitute.For(); + _gitHubProvider = new GitHubProvider(_gitHubClient, _mapper); + } + + // Assets + [Test] + public async Task Should_Delete_Asset() + { + _gitHubClient.Repository.Release.DeleteAsset(_owner, _repository, _assetId) + .Returns(Task.FromResult); + + await _gitHubProvider.DeleteAssetAsync(_owner, _repository, _assetId).ConfigureAwait(false); + + await _gitHubClient.Repository.Release.Received(1).DeleteAsset(_owner, _repository, _assetId).ConfigureAwait(false); + } + + [Test] + public async Task Should_Throw_An_Exception_On_Deleting_Asset_For_Non_Existing_Id() + { + _gitHubClient.Repository.Release.DeleteAsset(_owner, _repository, _assetId) + .Returns(Task.FromException(_notFoundException)); + + var ex = await Should.ThrowAsync(() => _gitHubProvider.DeleteAssetAsync(_owner, _repository, _assetId)).ConfigureAwait(false); + ex.Message.ShouldBe(_notFoundException.Message); + ex.InnerException.ShouldBe(_notFoundException); + } + + [Test] + public async Task Should_Throw_An_Exception_On_Deleting_Asset() + { + _gitHubClient.Repository.Release.DeleteAsset(_owner, _repository, _assetId) + .Returns(Task.FromException(_exception)); + + var ex = await Should.ThrowAsync(() => _gitHubProvider.DeleteAssetAsync(_owner, _repository, _assetId)).ConfigureAwait(false); + ex.Message.ShouldBe(_exception.Message); + ex.InnerException.ShouldBe(_exception); + } + + [Test] + public async Task Should_Upload_Asset() + { + var octokitRelease = new Octokit.Release(); + var octokitReleaseAssetUpload = new Octokit.ReleaseAssetUpload(); + + _mapper.Map(_release) + .Returns(octokitRelease); + + _mapper.Map(_releaseAssetUpload) + .Returns(octokitReleaseAssetUpload); + + _gitHubClient.Repository.Release.UploadAsset(octokitRelease, octokitReleaseAssetUpload) + .Returns(Task.FromResult(new Octokit.ReleaseAsset())); + + await _gitHubProvider.UploadAssetAsync(_release, _releaseAssetUpload).ConfigureAwait(false); + + _mapper.Received(1).Map(_release); + _mapper.Received(1).Map(_releaseAssetUpload); + await _gitHubClient.Repository.Release.Received(1).UploadAsset(octokitRelease, octokitReleaseAssetUpload).ConfigureAwait(false); + } + + [Test] + public async Task Should_Throw_An_Exception_On_Uploading_Asset_For_Non_Existing_Release() + { + _mapper.Map(_release) + .Returns(new Octokit.Release()); + + _mapper.Map(_releaseAssetUpload) + .Returns(new Octokit.ReleaseAssetUpload()); + + _gitHubClient.Repository.Release.UploadAsset(Arg.Any(), Arg.Any()) + .Returns(Task.FromException(_notFoundException)); + + var ex = await Should.ThrowAsync(() => _gitHubProvider.UploadAssetAsync(_release, _releaseAssetUpload)).ConfigureAwait(false); + ex.Message.ShouldBe(_notFoundException.Message); + ex.InnerException.ShouldBe(_notFoundException); + } + + [Test] + public async Task Should_Throw_An_Exception_On_Uploading_Asset() + { + _mapper.Map(_release) + .Returns(new Octokit.Release()); + + _mapper.Map(_releaseAssetUpload) + .Returns(new Octokit.ReleaseAssetUpload()); + + _gitHubClient.Repository.Release.UploadAsset(Arg.Any(), Arg.Any()) + .Returns(Task.FromException(_exception)); + + var ex = await Should.ThrowAsync(() => _gitHubProvider.UploadAssetAsync(_release, _releaseAssetUpload)).ConfigureAwait(false); + ex.Message.ShouldBe(_exception.Message); + ex.InnerException.ShouldBe(_exception); + } + + // Commits + [Test] + public async Task Should_Get_Commits_Count() + { + var commitsCount = 12; + + _gitHubClient.Repository.Commit.Compare(_owner, _repository, _base, _head) + .Returns(Task.FromResult(new CompareResult(null, null, null, null, null, null, null, null, commitsCount, 0, 0, null, null))); + + var result = await _gitHubProvider.GetCommitsCount(_owner, _repository, _base, _head).ConfigureAwait(false); + result.ShouldBe(commitsCount); + + await _gitHubClient.Repository.Commit.Received(1).Compare(_owner, _repository, _base, _head).ConfigureAwait(false); + } + + [Test] + public async Task Should_Get_Commits_Count_Zero_If_No_Commits_Found() + { + _gitHubClient.Repository.Commit.Compare(_owner, _repository, _base, _head) + .Returns(Task.FromException(_notFoundException)); + + var result = await _gitHubProvider.GetCommitsCount(_owner, _repository, _base, _head).ConfigureAwait(false); + result.ShouldBe(0); + + await _gitHubClient.Repository.Commit.Received(1).Compare(_owner, _repository, _base, _head).ConfigureAwait(false); + } + + [Test] + public async Task Should_Throw_An_Exception_On_Getting_Commits_Count() + { + _gitHubClient.Repository.Commit.Compare(_owner, _repository, _base, _head) + .Returns(Task.FromException(_exception)); + + var ex = await Should.ThrowAsync(() => _gitHubProvider.GetCommitsCount(_owner, _repository, _base, _head)).ConfigureAwait(false); + ex.Message.ShouldContain(_exception.Message); + ex.InnerException.ShouldBeSameAs(_exception); + } + + [TestCase("0.1.0", null, "https://github.com/owner/repository/commits/0.1.0")] + [TestCase("0.5.0", "0.1.0", "https://github.com/owner/repository/compare/0.1.0...0.5.0")] + public void Should_Get_Commits_Url(string head, string @base, string expectedResult) + { + var result = _gitHubProvider.GetCommitsUrl(_owner, _repository, head, @base); + result.ShouldBe(expectedResult); + } + + [TestCaseSource(nameof(GetCommitsUrl_TestCases))] + public void Should_Throw_An_Exception_If_Parameter_Is_Invalid(string owner, string repository, string head, string paramName, Type expectedException) + { + var ex = Should.Throw(() => _gitHubProvider.GetCommitsUrl(owner, repository, head), expectedException); + ex.Message.ShouldContain(paramName); + } + + public static IEnumerable GetCommitsUrl_TestCases() + { + var typeArgumentException = typeof(ArgumentException); + var typeArgumentNullException = typeof(ArgumentNullException); + + yield return new TestCaseData(null, null, null, "owner", typeArgumentNullException); + yield return new TestCaseData("", null, null, "owner", typeArgumentException); + yield return new TestCaseData(" ", null, null, "owner", typeArgumentException); + + yield return new TestCaseData("owner", null, null, "repository", typeArgumentNullException); + yield return new TestCaseData("owner", "", null, "repository", typeArgumentException); + yield return new TestCaseData("owner", " ", null, "repository", typeArgumentException); + + yield return new TestCaseData("owner", "repository", null, "head", typeArgumentNullException); + yield return new TestCaseData("owner", "repository", "", "head", typeArgumentException); + yield return new TestCaseData("owner", "repository", " ", "head", typeArgumentException); + } + + // Issues + [Test] + public async Task Should_Create_Issue_Comment() + { + _gitHubClient.Issue.Comment.Create(_owner, _repository, _issueNumber, _issueComment) + .Returns(Task.FromResult(new Octokit.IssueComment())); + + await _gitHubProvider.CreateIssueCommentAsync(_owner, _repository, _issueNumber, _issueComment).ConfigureAwait(false); + + await _gitHubClient.Issue.Comment.Received(1).Create(_owner, _repository, _issueNumber, _issueComment).ConfigureAwait(false); + } + + [Test] + public async Task Should_Throw_An_Exception_On_Creating_Issue_Comment_For_Non_Existing_Issue_Number() + { + _gitHubClient.Issue.Comment.Create(_owner, _repository, _issueNumber, _issueComment) + .Returns(Task.FromException(_notFoundException)); + + var ex = await Should.ThrowAsync(() => _gitHubProvider.CreateIssueCommentAsync(_owner, _repository, _issueNumber, _issueComment)).ConfigureAwait(false); + ex.Message.ShouldBe(_notFoundException.Message); + ex.InnerException.ShouldBe(_notFoundException); + } + + [Test] + public async Task Should_Throw_An_Exception_On_Creating_Issue_Comment() + { + _gitHubClient.Issue.Comment.Create(_owner, _repository, _issueNumber, _issueComment) + .Returns(Task.FromException(_exception)); + + var ex = await Should.ThrowAsync(() => _gitHubProvider.CreateIssueCommentAsync(_owner, _repository, _issueNumber, _issueComment)).ConfigureAwait(false); + ex.Message.ShouldBe(_exception.Message); + ex.InnerException.ShouldBe(_exception); + } + + [TestCase(ItemStateFilter.Open)] + [TestCase(ItemStateFilter.Closed)] + [TestCase(ItemStateFilter.All)] + public async Task Should_Get_Issues_For_Milestone(ItemStateFilter itemStateFilter) + { + var issues = new List(); + + _gitHubClient.Issue.GetAllForRepository(_owner, _repository, Arg.Any(), Arg.Any()) + .Returns(Task.FromResult((IReadOnlyList)new List())); + + _mapper.Map>(Arg.Any()) + .Returns(issues); + + var result = await _gitHubProvider.GetIssuesAsync(_owner, _repository, _milestoneNumber, itemStateFilter).ConfigureAwait(false); + result.ShouldBeSameAs(issues); + + await _gitHubClient.Issue.Received(1).GetAllForRepository( + _owner, + _repository, + Arg.Is(o => o.Milestone == _milestoneNumberString && o.State == (Octokit.ItemStateFilter)itemStateFilter), + Arg.Any()).ConfigureAwait(false); + + _mapper.ReceivedWithAnyArgs(1).Map>(default); + } + + [Test] + public async Task Should_Throw_An_Exception_On_Getting_Issues_For_Non_Existent_Milestone() + { + _gitHubClient.Issue.GetAllForRepository(_owner, _repository, Arg.Any(), Arg.Any()) + .Returns(Task.FromException>(_exception)); + + var ex = await Should.ThrowAsync(() => _gitHubProvider.GetIssuesAsync(_owner, _repository, 1)).ConfigureAwait(false); + ex.Message.ShouldBe(_exception.Message); + ex.InnerException.ShouldBe(_exception); + } + + [Test] + public async Task Should_Get_Issue_Comments() + { + var comments = new List(); + + _gitHubClient.Issue.Comment.GetAllForIssue(_owner, _repository, _issueNumber, Arg.Any()) + .Returns(Task.FromResult((IReadOnlyList)new List())); + + _mapper.Map>(Arg.Any()) + .Returns(comments); + + var result = await _gitHubProvider.GetIssueCommentsAsync(_owner, _repository, _issueNumber).ConfigureAwait(false); + result.ShouldBeSameAs(comments); + + await _gitHubClient.Issue.Comment.Received(1).GetAllForIssue(_owner, _repository, _issueNumber, Arg.Any()).ConfigureAwait(false); + _mapper.Received(1).Map>(Arg.Any()); + } + + [Test] + public async Task Should_Throw_An_Exception_On_Getting_Issue_Comments_For_Non_Existing_Issue_Number() + { + _gitHubClient.Issue.Comment.GetAllForIssue(_owner, _repository, _issueNumber, Arg.Any()) + .Returns(Task.FromException>(_notFoundException)); + + var ex = await Should.ThrowAsync(() => _gitHubProvider.GetIssueCommentsAsync(_owner, _repository, _issueNumber)).ConfigureAwait(false); + ex.Message.ShouldBe(_notFoundException.Message); + ex.InnerException.ShouldBe(_notFoundException); + } + + [Test] + public async Task Should_Throw_An_Exception_On_Getting_Issue_Comments() + { + _gitHubClient.Issue.Comment.GetAllForIssue(_owner, _repository, _issueNumber, Arg.Any()) + .Returns(Task.FromException>(_exception)); + + var ex = await Should.ThrowAsync(() => _gitHubProvider.GetIssueCommentsAsync(_owner, _repository, _issueNumber)).ConfigureAwait(false); + ex.Message.ShouldBe(_exception.Message); + ex.InnerException.ShouldBe(_exception); + } + + // Labels + [Test] + public async Task Should_Create_Label() + { + _mapper.Map(Arg.Any