diff --git a/tests/Promote.NuGet.Tests/Promote.NuGet.Tests.csproj b/tests/Promote.NuGet.Tests/Promote.NuGet.Tests.csproj index aa3e26f..6698a6a 100644 --- a/tests/Promote.NuGet.Tests/Promote.NuGet.Tests.csproj +++ b/tests/Promote.NuGet.Tests/Promote.NuGet.Tests.csproj @@ -1,5 +1,6 @@ + \ No newline at end of file diff --git a/tests/Promote.NuGet.Tests/PromoteNugetProcessRunner.cs b/tests/Promote.NuGet.Tests/PromoteNugetProcessRunner.cs new file mode 100644 index 0000000..ea1255f --- /dev/null +++ b/tests/Promote.NuGet.Tests/PromoteNugetProcessRunner.cs @@ -0,0 +1,125 @@ +using System.Diagnostics; +using System.IO; + +namespace Promote.NuGet.Tests; + +public static class PromoteNugetProcessRunner +{ + public sealed class ProcessWrapper : IAsyncDisposable + { + public Process Process { get; } + + public ProcessWrapper(Process process) + { + Process = process; + } + + public void WaitForExit() + { + Process.WaitForExit(); + } + + public bool WaitForExit(int milliseconds) + { + return Process.WaitForExit(milliseconds); + } + + public Task WaitForExitAsync(CancellationToken cancellationToken = default) + { + return Process.WaitForExitAsync(cancellationToken); + } + + public int ExitCode => Process.ExitCode; + + public StreamReader StandardError => Process.StandardError; + + public StreamReader StandardOutput => Process.StandardOutput; + + public async ValueTask DisposeAsync() + { + if (Process.HasExited == false) + { + TestContext.WriteLine("The process is still running. Dumping its output and killing the process."); + TestContext.WriteLine("Error output:"); + TestContext.WriteLine(await Process.StandardError.ReadToEndAsync()); + TestContext.WriteLine("Standard output:"); + TestContext.WriteLine(await Process.StandardOutput.ReadToEndAsync()); + + TestContext.WriteLine("Killing..."); + Process.Kill(); + await Process.WaitForExitAsync(); + + TestContext.WriteLine("The process is stopped."); + } + + Process.Dispose(); + } + } + + public record ProcessRunResult(int ExitCode, IReadOnlyCollection StdOutput, IReadOnlyCollection StdError); + + public static async Task RunForResultAsync(params string[] arguments) + { + await using var process = await RunToExitAsync(arguments); + + var stdOutput = new List(); + while (await process.StandardOutput.ReadLineAsync() is { } line) + { + stdOutput.Add(line); + } + + var stdError = new List(); + while (await process.StandardError.ReadLineAsync() is { } line) + { + stdError.Add(line); + } + + return new ProcessRunResult(process.ExitCode, stdOutput, stdError); + } + + public static async Task RunToExitAsync(params string[] arguments) + { + var cancellationToken = TestContext.CurrentContext.CancellationToken; + + var process = Run(arguments); + + await process.WaitForExitAsync(cancellationToken); + + return process; + } + + public static ProcessWrapper Run(params string[] arguments) + { + var processStartInfo = new ProcessStartInfo + { + FileName = "dotnet", + ArgumentList = { "Promote.NuGet.dll" }, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + + foreach (var argument in arguments) + { + processStartInfo.ArgumentList.Add(argument); + } + + Process? process = null; + try + { + process = Process.Start(processStartInfo); + + if (process == null) + { + Assert.Fail("Failed to run the process"); + Environment.FailFast("UNREACHABLE"); + } + + return new ProcessWrapper(process); + } + catch + { + process?.Dispose(); + throw; + } + } +} diff --git a/tests/Promote.NuGet.Tests/PromoteSinglePackageTests.cs b/tests/Promote.NuGet.Tests/PromoteSinglePackageTests.cs new file mode 100644 index 0000000..e386245 --- /dev/null +++ b/tests/Promote.NuGet.Tests/PromoteSinglePackageTests.cs @@ -0,0 +1,66 @@ +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; +using Promote.NuGet.Feeds; +using Promote.NuGet.TestInfrastructure; + +namespace Promote.NuGet.Tests; + +[TestFixture] +public class PromoteSinglePackageTests +{ + [Test, CancelAfter(30_000)] + public async Task Promotes_a_package_with_its_dependencies_to_destination_feed() + { + await using var destinationFeed = await LocalNugetFeed.Create(); + + // Act + var result = await PromoteNugetProcessRunner.RunForResultAsync( + "promote", + "package", + "System.Runtime", + "--version", "4.3.0", + "--destination", destinationFeed.FeedUrl, + "--destination-api-key", destinationFeed.ApiKey + ); + + var destinationFeedDescriptor = new NuGetRepositoryDescriptor(destinationFeed.FeedUrl, destinationFeed.ApiKey); + var destinationRepo = new NuGetRepository(destinationFeedDescriptor, NullSourceCacheContext.Instance, TestNuGetLogger.Instance); + + // Assert + result.StdOutput.Should().StartWith( + new[] + { + "Resolving packages to promote:", + "└── System.Runtime 4.3.0" + } + ); + + result.StdOutput.Should().ContainInConsecutiveOrder( + "Found 3 package(s) to promote:", + "├── Microsoft.NETCore.Platforms 1.1.0", + "├── Microsoft.NETCore.Targets 1.1.0", + "└── System.Runtime 4.3.0" + ); + + result.StdOutput.Should().ContainInOrder( + "(1/3) Promote Microsoft.NETCore.Platforms 1.1.0", + "(2/3) Promote Microsoft.NETCore.Targets 1.1.0", + "(3/3) Promote System.Runtime 4.3.0", + "3 package(s) promoted." + ); + + result.StdError.Should().BeEmpty(); + result.ExitCode.Should().Be(0); + + await AssertContainsVersions(destinationRepo, "System.Runtime", new[] { new NuGetVersion(4, 3, 0) }); + await AssertContainsVersions(destinationRepo, "Microsoft.NETCore.Platforms", new[] { new NuGetVersion(1, 1, 0) }); + await AssertContainsVersions(destinationRepo, "Microsoft.NETCore.Targets", new[] { new NuGetVersion(1, 1, 0) }); + } + + private static async Task AssertContainsVersions(INuGetRepository repo, string packageId, params NuGetVersion[] expectedVersions) + { + var netCorePlatformsPackages = await repo.Packages.GetAllVersions(packageId); + netCorePlatformsPackages.IsSuccess.Should().BeTrue(); + netCorePlatformsPackages.Value.Should().Contain(expectedVersions); + } +} diff --git a/tests/Promote.NuGet.Tests/ToolVersionTests.cs b/tests/Promote.NuGet.Tests/ToolVersionTests.cs new file mode 100644 index 0000000..edc462e --- /dev/null +++ b/tests/Promote.NuGet.Tests/ToolVersionTests.cs @@ -0,0 +1,22 @@ +using System.Diagnostics; + +namespace Promote.NuGet.Tests; + +[TestFixture] +public class ToolVersionTests +{ + [Test, CancelAfter(10_000)] + public async Task Returns_version_of_the_tool() + { + var expectedVersion = FileVersionInfo.GetVersionInfo(typeof(Program).Assembly.Location).ProductVersion ?? string.Empty; + var expectedVersionLines = expectedVersion.Chunk(80).Select(x => new string(x)).ToList(); + + // Act + var result = await PromoteNugetProcessRunner.RunForResultAsync("--version"); + + // Assert + result.StdOutput.Should().BeEquivalentTo(expectedVersionLines); + result.StdError.Should().BeEmpty(); + result.ExitCode.Should().Be(0); + } +}