Add unit tests for new controller
This commit is contained in:
Родитель
939a4ebf30
Коммит
6b253cf45c
|
@ -0,0 +1,23 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 120
|
||||
charset = utf-8
|
||||
|
||||
[*.{csproj,props,targets,DotSettings}]
|
||||
indent_size = 2
|
||||
|
||||
[*.cs]
|
||||
indent_size = 4
|
||||
|
||||
[*.yaml]
|
||||
indent_size = 2
|
||||
|
||||
[*.ps1]
|
||||
indent_size = 4
|
||||
|
||||
[*.json]
|
||||
indent_size = 2
|
|
@ -5,14 +5,19 @@ VisualStudioVersion = 16.6.30114.105
|
|||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C742A7B8-80CA-4365-85CA-C29AA744CE54}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCoreToolService", "src\NetCoreToolService\NetCoreToolService.csproj", "{1462EDFE-F1FC-48C2-80C1-917317EE3C97}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Common.Utils", "src\Common.Utils\Steeltoe.Common.Utils.csproj", "{3E82184B-FA14-4B24-9ED0-A823465B6AB9}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.NetCoreToolService", "src\NetCoreToolService\Steeltoe.NetCoreToolService.csproj", "{1462EDFE-F1FC-48C2-80C1-917317EE3C97}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{410C0E72-737F-4168-AECA-2F6D19EE86D5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCoreToolService.Test", "test\NetCoreToolService.Test\NetCoreToolService.Test.csproj", "{6BD6C793-E555-475F-A2E1-12D7F3F56A3B}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Common.Utils.Test", "test\Common.Utils.Test\Steeltoe.Common.Utils.Test.csproj", "{06247239-19D8-4FB5-99F1-F399346ADE4F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.NetCoreToolService.Test", "test\NetCoreToolService.Test\Steeltoe.NetCoreToolService.Test.csproj", "{6BD6C793-E555-475F-A2E1-12D7F3F56A3B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C37528B8-BDD1-440F-B69A-AD57F939620C}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.gitconfig = .gitconfig
|
||||
.gitignore = .gitignore
|
||||
Directory.Build.props = Directory.Build.props
|
||||
|
@ -57,6 +62,30 @@ Global
|
|||
{6BD6C793-E555-475F-A2E1-12D7F3F56A3B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6BD6C793-E555-475F-A2E1-12D7F3F56A3B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6BD6C793-E555-475F-A2E1-12D7F3F56A3B}.Release|x86.Build.0 = Release|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|x64.Build.0 = Release|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|x86.Build.0 = Release|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -64,6 +93,8 @@ Global
|
|||
GlobalSection(NestedProjects) = preSolution
|
||||
{1462EDFE-F1FC-48C2-80C1-917317EE3C97} = {C742A7B8-80CA-4365-85CA-C29AA744CE54}
|
||||
{6BD6C793-E555-475F-A2E1-12D7F3F56A3B} = {410C0E72-737F-4168-AECA-2F6D19EE86D5}
|
||||
{3E82184B-FA14-4B24-9ED0-A823465B6AB9} = {C742A7B8-80CA-4365-85CA-C29AA744CE54}
|
||||
{06247239-19D8-4FB5-99F1-F399346ADE4F} = {410C0E72-737F-4168-AECA-2F6D19EE86D5}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D8EFB01A-92BF-418B-B2B2-A12045B772E2}
|
|
@ -2,7 +2,22 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<SteeltoeNetCoreToolServiceVersion>0.0.1</SteeltoeNetCoreToolServiceVersion>
|
||||
<SwashbuckleVersion>5.6.*</SwashbuckleVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<MicrosoftExtensionsVersion>5.0.*</MicrosoftExtensionsVersion>
|
||||
<SwashbuckleVersion>5.6.*</SwashbuckleVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<CoverletCollectorVersion>1.3.*</CoverletCollectorVersion>
|
||||
<CoverletMsBuildVersion>2.9.*</CoverletMsBuildVersion>
|
||||
<FluentAssertionsVersion>5.10.*</FluentAssertionsVersion>
|
||||
<FluentAssertionsJsonVersion>5.5.*</FluentAssertionsJsonVersion>
|
||||
<MicrosoftAspNetCoreMvcTestingVersion>3.1.*</MicrosoftAspNetCoreMvcTestingVersion>
|
||||
<MicrosoftNetTestSdkVersion>16.7.*</MicrosoftNetTestSdkVersion>
|
||||
<MoqVersion>4.14.*</MoqVersion>
|
||||
<XunitVersion>2.4.*</XunitVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Steeltoe.Common.Utils.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// The exception that is thrown when a system error occurs running a command.
|
||||
/// </summary>
|
||||
public class CommandException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommandException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">Foo.</param>
|
||||
/// <param name="innerException">Foo bar.</param>
|
||||
public CommandException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Steeltoe.Common.Utils.Diagnostics
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public class CommandExecutor : ICommandExecutor
|
||||
{
|
||||
private static int _commandCounter;
|
||||
|
||||
private readonly ILogger<CommandExecutor> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommandExecutor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Injected logger.</param>
|
||||
public CommandExecutor(ILogger<CommandExecutor> logger = null)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<CommandResult> ExecuteAsync(string command, string workingDirectory = null, int timeout = -1)
|
||||
{
|
||||
var commandId = ++_commandCounter;
|
||||
using var process = new Process();
|
||||
var arguments = command.Split(new[] { ' ' }, 2);
|
||||
process.StartInfo.FileName = arguments[0];
|
||||
if (arguments.Length > 1)
|
||||
{
|
||||
process.StartInfo.Arguments = arguments[1];
|
||||
}
|
||||
|
||||
if (workingDirectory != null)
|
||||
{
|
||||
process.StartInfo.WorkingDirectory = workingDirectory;
|
||||
}
|
||||
|
||||
process.StartInfo.RedirectStandardInput = true;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
|
||||
var output = new StringBuilder();
|
||||
var outputCloseEvent = new TaskCompletionSource<bool>();
|
||||
process.OutputDataReceived += (_, e) =>
|
||||
{
|
||||
if (e.Data is null)
|
||||
{
|
||||
outputCloseEvent.SetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
output.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
var error = new StringBuilder();
|
||||
var errorCloseEvent = new TaskCompletionSource<bool>();
|
||||
process.ErrorDataReceived += (_, e) =>
|
||||
{
|
||||
if (e.Data is null)
|
||||
{
|
||||
errorCloseEvent.SetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
error.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
_logger?.LogDebug("[{CommandId}] command: {Command}", commandId, command);
|
||||
try
|
||||
{
|
||||
if (!process.Start())
|
||||
{
|
||||
_logger?.LogDebug("[{CommandId}] failed to start: {Error}", commandId, "no details available");
|
||||
throw new Exception($"'{command}' failed to start; no details available");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogDebug("[{CommandId}] failed to start: {Error}", commandId, ex.Message);
|
||||
throw new CommandException($"'{command}' failed to start: {ex.Message}", ex);
|
||||
}
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
var waitForExit = Task.Run(() => process.WaitForExit(timeout));
|
||||
var processTask = Task.WhenAll(waitForExit, outputCloseEvent.Task, errorCloseEvent.Task);
|
||||
if (await Task.WhenAny(Task.Delay(timeout), processTask) == processTask && waitForExit.Result)
|
||||
{
|
||||
var result = new CommandResult
|
||||
{
|
||||
ExitCode = process.ExitCode, Output = output.ToString(), Error = error.ToString(),
|
||||
};
|
||||
_logger?.LogDebug("[{CommandId}] exit code: {ExitCode}", commandId, result.ExitCode);
|
||||
if (result.Output.Length > 0)
|
||||
{
|
||||
_logger?.LogDebug("[{CommandId}] stdout:\n{Output}", commandId, result.Output);
|
||||
}
|
||||
|
||||
if (result.Error.Length > 0)
|
||||
{
|
||||
_logger?.LogDebug("[{CommandId}] stderr:\n{Error}", commandId, result.Error);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
_logger?.LogDebug("[{CommandId}] timed out: {TimeOut}ms", commandId, timeout);
|
||||
throw new Exception($"'{process.StartInfo.FileName} {process.StartInfo.Arguments}' timed out");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Steeltoe.Common.Utils.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// An simple abstraction of a command result.
|
||||
/// </summary>
|
||||
public struct CommandResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the command exit code.
|
||||
/// </summary>
|
||||
public int ExitCode { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command exit STDOUT.
|
||||
/// </summary>
|
||||
public string Output { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command exit STDERR.
|
||||
/// </summary>
|
||||
public string Error { get; init; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Steeltoe.Common.Utils.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// A utility abstraction to simplify the running of commands.
|
||||
/// </summary>
|
||||
public interface ICommandExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute the command and return the result.
|
||||
/// </summary>
|
||||
/// <param name="command">Command to be executed.</param>
|
||||
/// <param name="workingDirectory">The directory that contains the command process.</param>
|
||||
/// <param name="timeout">The amount of time in milliseconds to wait for command to complete.</param>
|
||||
/// <returns>Command result.</returns>
|
||||
/// <exception cref="CommandException">If a process can not be started for command.</exception>
|
||||
Task<CommandResult> ExecuteAsync(string command, string workingDirectory = null, int timeout = -1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -4,45 +4,42 @@
|
|||
|
||||
using System.IO;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Utils.IO
|
||||
namespace Steeltoe.Common.Utils.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// A temp directory.
|
||||
/// A temporary directory.
|
||||
/// </summary>
|
||||
public sealed class TempDirectory : TempPath
|
||||
public class TempDirectory : TempPath
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TempDirectory"/> class.
|
||||
/// </summary>
|
||||
public TempDirectory()
|
||||
: this(true)
|
||||
/// <param name="prefix">Temporary directory prefix.</param>
|
||||
public TempDirectory(string prefix = null)
|
||||
: base(prefix)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TempDirectory"/> class.
|
||||
/// Creates the temporary directory.
|
||||
/// </summary>
|
||||
/// <param name="create">If true, create the directory.</param>
|
||||
public TempDirectory(bool create)
|
||||
protected override void InitializePath()
|
||||
{
|
||||
if (create)
|
||||
{
|
||||
Directory.CreateDirectory(FullName);
|
||||
}
|
||||
Directory.CreateDirectory(FullPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!Directory.Exists(FullName))
|
||||
if (!Directory.Exists(FullPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Directory.Delete(FullName, true);
|
||||
Directory.Delete(FullPath, true);
|
||||
}
|
||||
catch
|
||||
{
|
|
@ -4,31 +4,28 @@
|
|||
|
||||
using System.IO;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Utils.IO
|
||||
namespace Steeltoe.Common.Utils.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// A temp file.
|
||||
/// A temporary directory.
|
||||
/// </summary>
|
||||
public sealed class TempFile : TempPath
|
||||
public class TempFile : TempPath
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TempFile"/> class.
|
||||
/// </summary>
|
||||
public TempFile()
|
||||
: this(true)
|
||||
/// <param name="prefix">Temporary file prefix.</param>
|
||||
public TempFile(string prefix = null)
|
||||
: base(prefix)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TempFile"/> class.
|
||||
/// Creates the temporary file.
|
||||
/// </summary>
|
||||
/// <param name="create">If true, create file.</param>
|
||||
public TempFile(bool create)
|
||||
protected override void InitializePath()
|
||||
{
|
||||
if (create)
|
||||
{
|
||||
File.Create(FullName).Dispose();
|
||||
}
|
||||
File.Create(FullPath).Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -36,14 +33,14 @@ namespace Steeltoe.NetCoreToolService.Utils.IO
|
|||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!File.Exists(FullName))
|
||||
if (!File.Exists(FullPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(FullName);
|
||||
File.Delete(FullPath);
|
||||
}
|
||||
catch
|
||||
{
|
|
@ -4,22 +4,23 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Utils.IO
|
||||
namespace Steeltoe.Common.Utils.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// An abstraction of temp files.
|
||||
/// An abstraction of a temporary path, such as a file.
|
||||
/// </summary>
|
||||
public abstract class TempPath : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TempPath"/> class.
|
||||
/// </summary>
|
||||
protected TempPath()
|
||||
/// <param name="prefix">Temporary path prefix.</param>
|
||||
protected TempPath(string prefix = null)
|
||||
{
|
||||
Name = Guid.NewGuid().ToString();
|
||||
FullName = Path.Join(Path.GetTempPath(), Assembly.GetExecutingAssembly().GetName().Name, Name);
|
||||
Name = $"{prefix ?? string.Empty}{Guid.NewGuid()}";
|
||||
FullPath = $"{Path.GetTempPath()}{Path.DirectorySeparatorChar}{Name}";
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -31,17 +32,17 @@ namespace Steeltoe.NetCoreToolService.Utils.IO
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full name of this path.
|
||||
/// Gets the absolute path of the TempPath.
|
||||
/// </summary>
|
||||
public string FullName { get; }
|
||||
public string FullPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of this path.
|
||||
/// Gets the name of the TempPath.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the underlying temporary path is deleted.
|
||||
/// Ensures the temporary path is deleted.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -49,7 +50,7 @@ namespace Steeltoe.NetCoreToolService.Utils.IO
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the underlying temporary path is deleted.
|
||||
/// Ensures the temporary path is deleted.
|
||||
/// </summary>
|
||||
/// <param name="disposing">If disposing.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
|
@ -59,5 +60,17 @@ namespace Steeltoe.NetCoreToolService.Utils.IO
|
|||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subclasses should override and perform any path initialization here.
|
||||
/// </summary>
|
||||
protected virtual void InitializePath()
|
||||
{
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
InitializePath();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,58 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Steeltoe.NetCoreToolService.Services;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Archivers
|
||||
{
|
||||
/// <summary>
|
||||
/// An in-memory <see cref="IArchiverRegistry"/> implementation.
|
||||
/// </summary>
|
||||
public class ArchiverRegistry : Service, IArchiverRegistry
|
||||
{
|
||||
/* ----------------------------------------------------------------- *
|
||||
* fields *
|
||||
* ----------------------------------------------------------------- */
|
||||
|
||||
private readonly Dictionary<string, IArchiver> _archivers = new Dictionary<string, IArchiver>();
|
||||
|
||||
/* ----------------------------------------------------------------- *
|
||||
* constructors *
|
||||
* ----------------------------------------------------------------- */
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArchiverRegistry"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Injected logger.</param>
|
||||
public ArchiverRegistry(ILogger<ArchiverRegistry> logger)
|
||||
: base(logger)
|
||||
{
|
||||
Logger.LogInformation("Initializing archiver registry");
|
||||
Register(new ZipArchiver());
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- *
|
||||
* methods *
|
||||
* ----------------------------------------------------------------- */
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register(IArchiver archiver)
|
||||
{
|
||||
Logger.LogInformation(
|
||||
"Registering archiver: {Archiver} -> {ArchiverType}",
|
||||
archiver.Name,
|
||||
archiver.GetType());
|
||||
_archivers.Add(archiver.Name, archiver);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IArchiver Lookup(string packaging)
|
||||
{
|
||||
_archivers.TryGetValue(packaging, out var archiver);
|
||||
return archiver;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,13 +5,15 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Steeltoe.NetCoreToolService.Services;
|
||||
using Steeltoe.NetCoreToolService.Utils.IO;
|
||||
using Steeltoe.Common.Utils.Diagnostics;
|
||||
using Steeltoe.Common.Utils.IO;
|
||||
using Steeltoe.NetCoreToolService.Models;
|
||||
using Steeltoe.NetCoreToolService.Packagers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Controllers
|
||||
|
@ -20,21 +22,30 @@ namespace Steeltoe.NetCoreToolService.Controllers
|
|||
/// The controller for "dotnet new".
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Route("api/new")]
|
||||
public class NewController : ControllerBase
|
||||
{
|
||||
private readonly IArchiverRegistry _archiverRegistry;
|
||||
private const string DefaultOutput = "Sample";
|
||||
|
||||
private const string DefaultPackaging = "zip";
|
||||
|
||||
private readonly ICommandExecutor _commandExecutor;
|
||||
|
||||
private readonly ILogger<NewController> _logger;
|
||||
|
||||
private readonly Dictionary<string, IPackager> _packagers = new ()
|
||||
{
|
||||
{ "zip", new ZipPackager() },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="archiverRegistry">Injected registry of available archivers.</param>
|
||||
/// <param name="commandExecutor">Injected command.</param>
|
||||
/// <param name="logger">Injected logger.</param>
|
||||
public NewController(IArchiverRegistry archiverRegistry, ILogger<NewController> logger)
|
||||
public NewController(ICommandExecutor commandExecutor, ILogger<NewController> logger = null)
|
||||
{
|
||||
_archiverRegistry = archiverRegistry;
|
||||
_commandExecutor = commandExecutor;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -45,76 +56,7 @@ namespace Steeltoe.NetCoreToolService.Controllers
|
|||
[HttpGet]
|
||||
public async Task<ActionResult> GetTemplates()
|
||||
{
|
||||
var dict = await GetTemplateDictionary();
|
||||
return Ok(dict);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a generated project for the specified Net Core Tool template.
|
||||
/// </summary>
|
||||
/// <param name="template">Template name.</param>
|
||||
/// <param name="options">Template options.</param>
|
||||
/// <returns>Project archive.</returns>
|
||||
[HttpGet]
|
||||
[Route("{template}")]
|
||||
public async Task<ActionResult> GetProjectArchiveForTemplate(string template, string options)
|
||||
{
|
||||
var opts = options?.Split(',').Select(opt => opt.Trim()).ToList() ?? new List<string>();
|
||||
var pArgs = new List<string>() { "new", template };
|
||||
var name = opts.Find(opt => opt.StartsWith("output="))?.Split('=', 2)[1];
|
||||
if (name is null)
|
||||
{
|
||||
name = "Sample";
|
||||
pArgs.AddRange(new[] { "--output", name });
|
||||
}
|
||||
|
||||
pArgs.AddRange(opts.Select(opt => $"--{opt}"));
|
||||
|
||||
using var workDir = new TempDirectory();
|
||||
var pInfo = new ProcessStartInfo
|
||||
{
|
||||
Arguments = string.Join(' ', pArgs),
|
||||
WorkingDirectory = workDir.FullName,
|
||||
};
|
||||
|
||||
var result = await ProcessToResultAsync(pInfo);
|
||||
var ok = result as ContentResult;
|
||||
if (ok is null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!Directory.EnumerateFileSystemEntries(workDir.FullName).Any())
|
||||
{
|
||||
return NotFound($"template {template} does not exist");
|
||||
}
|
||||
|
||||
var archivalType = "zip";
|
||||
var archiver = _archiverRegistry.Lookup(archivalType);
|
||||
if (archiver is null)
|
||||
{
|
||||
return NotFound($"Packaging '{archivalType}' not found.");
|
||||
}
|
||||
|
||||
var archiveBytes = archiver.ToBytes(workDir.FullName);
|
||||
return File(archiveBytes, archiver.MimeType, $"{name}{archiver.FileExtension}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns "help" for the specified Net Core Tool template.
|
||||
/// </summary>
|
||||
/// <param name="template">Template name.</param>
|
||||
/// <returns>Template help.</returns>
|
||||
[HttpGet]
|
||||
[Route("{template}/help")]
|
||||
public async Task<ActionResult> GetTemplateHelp(string template)
|
||||
{
|
||||
var pInfo = new ProcessStartInfo
|
||||
{
|
||||
ArgumentList = { "new", template, "--help" },
|
||||
};
|
||||
|
||||
return await ProcessToResultAsync(pInfo);
|
||||
return Ok(await GetTemplateDictionary());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -122,138 +64,210 @@ namespace Steeltoe.NetCoreToolService.Controllers
|
|||
/// </summary>
|
||||
/// <param name="nuGetId">Template NuGet ID.</param>
|
||||
/// <returns>Information about the installed templates.</returns>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> InstallTemplate(string nuGetId)
|
||||
[HttpPut("nuget/{nuGetId}")]
|
||||
public async Task<ActionResult> InstallTemplates(string nuGetId)
|
||||
{
|
||||
if (nuGetId is null)
|
||||
await _commandExecutor.ExecuteAsync($"{NetCoreTool.Command} new --uninstall {nuGetId}");
|
||||
var oldTemplates = await GetTemplateDictionary();
|
||||
var installCommand = await _commandExecutor.ExecuteAsync($"{NetCoreTool.Command} new --install {nuGetId}");
|
||||
const string notFoundError = "error NU1101: ";
|
||||
if (installCommand.Output.Contains(notFoundError))
|
||||
{
|
||||
return BadRequest("missing NuGet ID");
|
||||
var start = installCommand.Output.IndexOf(notFoundError, StringComparison.Ordinal) +
|
||||
notFoundError.Length;
|
||||
var end = installCommand.Output.IndexOf('\n', start);
|
||||
return BadRequest(installCommand.Output[start..end].Trim());
|
||||
}
|
||||
|
||||
var preInstallTemplates = await GetTemplateDictionary();
|
||||
|
||||
var pInfo = new ProcessStartInfo
|
||||
var newTemplates = await GetTemplateDictionary();
|
||||
foreach (var oldTemplate in oldTemplates.Keys)
|
||||
{
|
||||
ArgumentList = { "new", "--install", nuGetId },
|
||||
};
|
||||
await ProcessToStringAsync(pInfo);
|
||||
|
||||
var postInstallTemplates = await GetTemplateDictionary();
|
||||
|
||||
foreach (var template in preInstallTemplates.Keys)
|
||||
{
|
||||
postInstallTemplates.Remove(template);
|
||||
newTemplates.Remove(oldTemplate);
|
||||
}
|
||||
|
||||
return Ok(postInstallTemplates);
|
||||
return CreatedAtAction(nameof(InstallTemplates), newTemplates);
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, TemplateInfo>> GetTemplateDictionary()
|
||||
/// <summary>
|
||||
/// Uninstalls the Net Core Tool templates for the specified NuGet ID.
|
||||
/// </summary>
|
||||
/// <param name="nuGetId">Template NuGet ID.</param>
|
||||
/// <returns>Information about the installed templates.</returns>
|
||||
[HttpDelete("nuget/{nuGetId}")]
|
||||
public async Task<ActionResult> UninstallTemplates(string nuGetId)
|
||||
{
|
||||
var pInfo = new ProcessStartInfo
|
||||
var oldTemplates = await GetTemplateDictionary();
|
||||
var uninstallCommand =
|
||||
await _commandExecutor.ExecuteAsync($"{NetCoreTool.Command} new --uninstall {nuGetId}");
|
||||
if (uninstallCommand.Output.Contains($"Could not find something to uninstall"))
|
||||
{
|
||||
ArgumentList = { "new", "--list" },
|
||||
};
|
||||
var listing = await ProcessToStringAsync(pInfo);
|
||||
var lines = listing.Split('\n').ToList().FindAll(line => !string.IsNullOrWhiteSpace(line));
|
||||
return NotFound($"No templates with NuGet ID '{nuGetId}' installed.");
|
||||
}
|
||||
|
||||
var newTemplates = await GetTemplateDictionary();
|
||||
foreach (var newTemplate in newTemplates.Keys)
|
||||
{
|
||||
oldTemplates.Remove(newTemplate);
|
||||
}
|
||||
|
||||
return Ok(oldTemplates);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns "help" for the specified Net Core Tool template.
|
||||
/// </summary>
|
||||
/// <param name="template">Template name.</param>
|
||||
/// <returns>Template help.</returns>
|
||||
[HttpGet("{template}/help")]
|
||||
public async Task<ActionResult> GetTemplateHelp(string template)
|
||||
{
|
||||
var helpCommand = await _commandExecutor.ExecuteAsync($"{NetCoreTool.Command} new {template} --help");
|
||||
if (helpCommand.ExitCode != 0)
|
||||
{
|
||||
var start = helpCommand.Error.IndexOf("No templates found", StringComparison.Ordinal);
|
||||
var end = helpCommand.Error.IndexOf('\n', start);
|
||||
return NotFound(helpCommand.Error[start..end].Trim());
|
||||
}
|
||||
|
||||
return Ok(helpCommand.Output.Trim());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a generated project for the specified Net Core Tool template.
|
||||
/// </summary>
|
||||
/// <param name="template">Template name.</param>
|
||||
/// <param name="options">Template options.</param>
|
||||
/// <param name="packaging">Project packaging, e.g. zip.</param>
|
||||
/// <returns>Project archive.</returns>
|
||||
[HttpGet]
|
||||
[Route("{template}")]
|
||||
public async Task<ActionResult> GetTemplateProject(
|
||||
string template,
|
||||
string options = null,
|
||||
string packaging = DefaultPackaging)
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
try
|
||||
{
|
||||
var output = DefaultOutput;
|
||||
var optionList = new List<string>();
|
||||
if (options is not null)
|
||||
{
|
||||
foreach (var option in options.Split(','))
|
||||
{
|
||||
if (option.Contains('='))
|
||||
{
|
||||
var nvp = option.Split('=', 2);
|
||||
if (nvp[0].Equals("output"))
|
||||
{
|
||||
output = nvp[1];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
optionList.Add($"--{option}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!_packagers.TryGetValue(packaging, out var packager))
|
||||
{
|
||||
return BadRequest($"Unknown or unsupported packaging '{packaging}'.");
|
||||
}
|
||||
|
||||
using var projectDir = new TempDirectory("NetCoreToolService-");
|
||||
var commandLine = new StringBuilder();
|
||||
commandLine.Append(NetCoreTool.Command).Append(" new ").Append(template);
|
||||
commandLine.Append(" --output=").Append(output);
|
||||
foreach (var option in optionList)
|
||||
{
|
||||
commandLine.Append(' ').Append(option);
|
||||
}
|
||||
|
||||
var newCommand =
|
||||
await _commandExecutor.ExecuteAsync(commandLine.ToString(), projectDir.FullPath);
|
||||
|
||||
const string unknownTemplateError = "No templates found";
|
||||
if (newCommand.Error.Contains(unknownTemplateError))
|
||||
{
|
||||
return NotFound($"Template '{template}' not found.");
|
||||
}
|
||||
|
||||
const string invalidSwitchError = "Invalid input switch:";
|
||||
if (newCommand.Error.Contains(invalidSwitchError))
|
||||
{
|
||||
var start = newCommand.Error.IndexOf(invalidSwitchError, StringComparison.Ordinal) +
|
||||
invalidSwitchError.Length;
|
||||
start = newCommand.Error.IndexOf("--", start, StringComparison.Ordinal) + "--".Length;
|
||||
var end = newCommand.Error.IndexOf('\n', start);
|
||||
return NotFound($"Switch '{newCommand.Error[start..end]}' not found.");
|
||||
}
|
||||
|
||||
const string invalidParameterError = "Error: Invalid parameter(s):";
|
||||
if (newCommand.Error.Contains(invalidParameterError))
|
||||
{
|
||||
var start = newCommand.Error.IndexOf(invalidParameterError, StringComparison.Ordinal) +
|
||||
invalidParameterError.Length;
|
||||
start = newCommand.Error.IndexOf("--", start, StringComparison.Ordinal) + "--".Length;
|
||||
var end = newCommand.Error.IndexOf('\n', start);
|
||||
var nvp = newCommand.Error[start..end].Split(' ', 2);
|
||||
return NotFound($"Option '{nvp[0]}' parameter '{nvp[1]}' not found.");
|
||||
}
|
||||
|
||||
if (newCommand.ExitCode != 0)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, newCommand.Error.Trim());
|
||||
}
|
||||
|
||||
if (!newCommand.Output.Contains(" was created successfully."))
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, newCommand.Output.Trim());
|
||||
}
|
||||
|
||||
var package = packager.ToBytes(projectDir.FullPath);
|
||||
return File(package, packager.MimeType, $"{output}{packager.FileExtension}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
_logger?.LogDebug("Generated project in {Elapsed:m\\:s\\.fff}", stopwatch.Elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<TemplateDictionary> GetTemplateDictionary()
|
||||
{
|
||||
var listCommand = await _commandExecutor.ExecuteAsync($"{NetCoreTool.Command} new --list");
|
||||
|
||||
var lines = listCommand.Output.Split('\n').ToList()
|
||||
.FindAll(line => !string.IsNullOrWhiteSpace(line));
|
||||
|
||||
var headingIdx = lines.FindIndex(line => line.StartsWith("-"));
|
||||
var headings = lines[headingIdx].Split(" ");
|
||||
var nameColLength = headings[0].Length;
|
||||
var shortNameColStart = nameColLength + 2;
|
||||
var shortNameColLength = headings[1].Length;
|
||||
var languageColStart = shortNameColStart + shortNameColLength + 2;
|
||||
var languageColLength = headings[2].Length;
|
||||
var tagsColStart = languageColStart + languageColLength + 2;
|
||||
var tagsColLength = headings[3].Length;
|
||||
const int nameColStart = 0;
|
||||
var nameColEnd = nameColStart + headings[0].Length;
|
||||
var shortNameColStart = nameColEnd + 2;
|
||||
var shortNameColEnd = shortNameColStart + headings[1].Length;
|
||||
var languageColStart = shortNameColEnd + 2;
|
||||
var languageColEnd = languageColStart + headings[2].Length;
|
||||
var tagsColStart = languageColEnd + 2;
|
||||
var tagsColEnd = tagsColStart + headings[3].Length;
|
||||
lines = lines.GetRange(headingIdx + 1, lines.Count - headingIdx - 1);
|
||||
|
||||
var dict = new Dictionary<string, TemplateInfo>();
|
||||
var dict = new TemplateDictionary();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var templateInfo = new TemplateInfo();
|
||||
var template = line.Substring(shortNameColStart, shortNameColLength).Trim();
|
||||
templateInfo.Name = line.Substring(0, nameColLength).Trim();
|
||||
templateInfo.Languages = line.Substring(languageColStart, languageColLength).Trim();
|
||||
templateInfo.Tags = line.Substring(tagsColStart, tagsColLength).Trim();
|
||||
var template = line[shortNameColStart..shortNameColEnd].Trim();
|
||||
var templateInfo = new TemplateInfo
|
||||
{
|
||||
Name = line[nameColStart..nameColEnd].Trim(),
|
||||
Languages = line[languageColStart..languageColEnd].Trim(),
|
||||
Tags = line[tagsColStart.. Math.Min(tagsColEnd, line.Length)].Trim(),
|
||||
};
|
||||
dict.Add(template, templateInfo);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private async Task<string> ProcessToStringAsync(ProcessStartInfo processStartInfo)
|
||||
{
|
||||
processStartInfo.FileName = NetCoreTool.Command;
|
||||
TempDirectory workDir = null;
|
||||
if (string.IsNullOrEmpty(processStartInfo.WorkingDirectory))
|
||||
{
|
||||
workDir = new TempDirectory();
|
||||
processStartInfo.WorkingDirectory = workDir.FullName;
|
||||
}
|
||||
|
||||
processStartInfo.UseShellExecute = false;
|
||||
processStartInfo.RedirectStandardOutput = true;
|
||||
processStartInfo.RedirectStandardError = true;
|
||||
var guid = Path.GetFileName(processStartInfo.WorkingDirectory) ?? "unknown";
|
||||
_logger.LogInformation(
|
||||
"{Guid}: {Command} {Args}",
|
||||
guid,
|
||||
processStartInfo.FileName,
|
||||
processStartInfo.Arguments);
|
||||
var proc = Process.Start(processStartInfo);
|
||||
if (proc is null)
|
||||
{
|
||||
throw new ActionResultException(StatusCode(StatusCodes.Status503ServiceUnavailable));
|
||||
}
|
||||
|
||||
await proc.WaitForExitAsync();
|
||||
workDir?.Dispose();
|
||||
if (proc.ExitCode == 0)
|
||||
{
|
||||
var output = await proc.StandardOutput.ReadToEndAsync();
|
||||
_logger.LogInformation("{Guid}>\n{Output}", guid, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
var error = await proc.StandardError.ReadToEndAsync();
|
||||
_logger.LogInformation("{Guid}: {Error}", guid, error);
|
||||
throw new ActionResultException(NotFound(error));
|
||||
}
|
||||
|
||||
private async Task<ActionResult> ProcessToResultAsync(ProcessStartInfo processStartInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Content(await ProcessToStringAsync(processStartInfo));
|
||||
}
|
||||
catch (ActionResultException e)
|
||||
{
|
||||
return e.ActionResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class TemplateInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Languages { get; set; }
|
||||
|
||||
public string Tags { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[name={Name},languages={Languages},tags={Tags}";
|
||||
}
|
||||
}
|
||||
|
||||
internal class ActionResultException : Exception
|
||||
{
|
||||
internal ActionResultException(ActionResult actionResult)
|
||||
{
|
||||
ActionResult = actionResult;
|
||||
}
|
||||
|
||||
internal ActionResult ActionResult { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A convenience dictionary for template information.
|
||||
/// </summary>
|
||||
public class TemplateDictionary : Dictionary<string, TemplateInfo>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about a Net Core Tool template.
|
||||
/// </summary>
|
||||
public class TemplateInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the template.
|
||||
/// </summary>
|
||||
public string Name { get; internal init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the supported languages of the template.
|
||||
/// </summary>
|
||||
public string Languages { get; internal init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the template tags.
|
||||
/// </summary>
|
||||
public string Tags { get; internal init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[name={Name},languages={Languages},tags={Tags}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,33 +2,33 @@
|
|||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Services
|
||||
namespace Steeltoe.NetCoreToolService.Packagers
|
||||
{
|
||||
/// <summary>
|
||||
/// Contract for archiver implementations.
|
||||
/// Contract for packager implementations.
|
||||
/// </summary>
|
||||
public interface IArchiver
|
||||
public interface IPackager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name for the archiver, e.g. <c>"zip"</c>.
|
||||
/// Gets the name for the packager, e.g. <c>"zip"</c>.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file extension for the archive, e.g. <c>".zip"</c>.
|
||||
/// Gets the file extension for package, e.g. <c>".zip"</c>.
|
||||
/// </summary>
|
||||
string FileExtension { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mime type for the archive, e.g. <c>"application/zip"</c>.
|
||||
/// Gets the mime type for a package, e.g. <c>"application/zip"</c>.
|
||||
/// </summary>
|
||||
string MimeType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns an archive of the files rooted at <c>path</c> as a byte array.
|
||||
/// Returns a package of the files rooted at <c>path</c> as a byte array.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to be archived.</param>
|
||||
/// <returns>A byte array containing the archive.</returns>
|
||||
/// <param name="path">Path to be packaged.</param>
|
||||
/// <returns>A byte array containing the package.</returns>
|
||||
byte[] ToBytes(string path);
|
||||
}
|
||||
}
|
|
@ -2,17 +2,16 @@
|
|||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Steeltoe.NetCoreToolService.Services;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Archivers
|
||||
namespace Steeltoe.NetCoreToolService.Packagers
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IArchiver"/> implementation using the ZIP archive file format.
|
||||
/// An <see cref="IPackager"/> implementation using the ZIP archive file format.
|
||||
/// </summary>
|
||||
public class ZipArchiver : IArchiver
|
||||
public class ZipPackager : IPackager
|
||||
{
|
||||
/* ----------------------------------------------------------------- *
|
||||
* fields *
|
||||
|
@ -35,10 +34,10 @@ namespace Steeltoe.NetCoreToolService.Archivers
|
|||
* ----------------------------------------------------------------- */
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ZipArchiver"/> class.
|
||||
/// Initializes a new instance of the <see cref="ZipPackager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="compression">Compression level default <see cref="CompressionLevel.Fastest"/>.</param>
|
||||
public ZipArchiver(CompressionLevel compression = CompressionLevel.Fastest)
|
||||
public ZipPackager(CompressionLevel compression = CompressionLevel.Fastest)
|
||||
{
|
||||
_compression = compression;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Contract for archiver registry implementations.
|
||||
/// </summary>
|
||||
public interface IArchiverRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the archiver.
|
||||
/// </summary>
|
||||
/// <param name="value">The archiver to register.</param>
|
||||
void Register(IArchiver value);
|
||||
|
||||
/// <summary>
|
||||
/// Look for an archiver with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the archiver to loookup.</param>
|
||||
/// <returns>The named archiver,or <c>null</c> if no value found.</returns>
|
||||
IArchiver Lookup(string name);
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for services.
|
||||
/// </summary>
|
||||
public abstract class Service
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Service"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Injected logger.</param>
|
||||
protected Service(ILogger logger)
|
||||
{
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
protected ILogger Logger { get; }
|
||||
}
|
||||
}
|
|
@ -8,8 +8,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Steeltoe.NetCoreToolService.Archivers;
|
||||
using Steeltoe.NetCoreToolService.Services;
|
||||
using Steeltoe.Common.Utils.Diagnostics;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService
|
||||
{
|
||||
|
@ -43,7 +42,7 @@ namespace Steeltoe.NetCoreToolService
|
|||
options.JsonSerializerOptions.IgnoreNullValues = true;
|
||||
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||
});
|
||||
services.AddSingleton<IArchiverRegistry, ArchiverRegistry>();
|
||||
services.AddTransient<ICommandExecutor, CommandExecutor>();
|
||||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Steeltoe.NetCoreToolService", Version = "v0" });
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Steeltoe.NetCoreToolService</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="$(SwashbuckleVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common.Utils\Steeltoe.Common.Utils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -4,6 +4,7 @@
|
|||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information",
|
||||
"Steeltoe.Common.Utils": "Debug",
|
||||
"Steeltoe.NetCoreToolService": "Debug"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Steeltoe.Common.Utils.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
namespace Steeltoe.Common.Utils.Test.Diagnostics
|
||||
{
|
||||
public class CommandExecutorTest
|
||||
{
|
||||
private readonly CommandExecutor _commandExecutor = new();
|
||||
|
||||
[Fact]
|
||||
public async void SuccessfulCommandShouldReturn0()
|
||||
{
|
||||
var result = await _commandExecutor.ExecuteAsync("dotnet --help");
|
||||
Assert.Equal(0, result.ExitCode);
|
||||
Assert.Contains("Usage: dotnet", result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void UnsuccessfulCommandShouldNotReturn0()
|
||||
{
|
||||
var result = await _commandExecutor.ExecuteAsync("dotnet --no-such-option");
|
||||
Assert.NotEqual(0, result.ExitCode);
|
||||
Assert.Contains("Unknown option: --no-such-option", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void UnknownCommandShouldThrowException()
|
||||
{
|
||||
Task Act() => _commandExecutor.ExecuteAsync("no-such-command");
|
||||
var exc = await Assert.ThrowsAsync<CommandException>(Act);
|
||||
Assert.Contains("'no-such-command' failed to start", exc.Message);
|
||||
Assert.NotNull(exc.InnerException);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp3.1;net5.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\..\..\..\versions.props" />
|
||||
<Import Project="..\..\..\..\sharedtest.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="xunit.runner.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Common.IO\Steeltoe.Common.IO.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,31 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Steeltoe.Common.Utils.IO;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace Steeltoe.Common.Utils.Test.IO
|
||||
{
|
||||
public class TempDirectoryTest
|
||||
{
|
||||
[Fact]
|
||||
public void TempDirectoryRemovesItself()
|
||||
{
|
||||
var tempDir = new TempDirectory();
|
||||
Assert.True(Directory.Exists(tempDir.FullPath));
|
||||
File.Create(Path.Join(tempDir.FullPath, "foo")).Dispose();
|
||||
tempDir.Dispose();
|
||||
Assert.False(Directory.Exists(tempDir.FullPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TempDirectoryCanBePrefixed()
|
||||
{
|
||||
const string prefix = "XXX-";
|
||||
using var tempDir = new TempDirectory(prefix);
|
||||
Assert.StartsWith(prefix, tempDir.Name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Steeltoe.Common.Utils.IO;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace Steeltoe.Common.Utils.Test.IO
|
||||
{
|
||||
public class TempFileTest
|
||||
{
|
||||
[Fact]
|
||||
public void TempFileRemovesItself()
|
||||
{
|
||||
var tempFile = new TempFile();
|
||||
Assert.True(File.Exists(tempFile.FullPath));
|
||||
tempFile.Dispose();
|
||||
Assert.False(File.Exists(tempFile.FullPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TempFileCanBePrefixed()
|
||||
{
|
||||
const string prefix = "XXX-";
|
||||
using var tempFile = new TempFile(prefix);
|
||||
Assert.StartsWith(prefix, tempFile.Name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Common.Utils\Steeltoe.Common.Utils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -3,15 +3,21 @@
|
|||
<Import Project="..\Directory.Build.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.*" />
|
||||
<PackageReference Include="coverlet.msbuild" Version="2.9.*" />
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.*" />
|
||||
<PackageReference Include="FluentAssertions.Json" Version="5.5.*" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.*" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.*" />
|
||||
<PackageReference Include="Moq" Version="4.14.*" />
|
||||
<PackageReference Include="xunit" Version="2.4.*" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.*" />
|
||||
<PackageReference Include="coverlet.collector" Version="$(CoverletCollectorVersion)">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.msbuild" Version="$(CoverletMsBuildVersion)" />
|
||||
<PackageReference Include="FluentAssertions" Version="$(FluentAssertionsVersion)" />
|
||||
<PackageReference Include="FluentAssertions.Json" Version="$(FluentAssertionsJsonVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(MicrosoftAspNetCoreMvcTestingVersion)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
|
||||
<PackageReference Include="Moq" Version="$(MoqVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Steeltoe.NetCoreToolService.Archivers;
|
||||
using Xunit;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Test.Archivers
|
||||
{
|
||||
public class ArchiverRegistryTests
|
||||
{
|
||||
/* ----------------------------------------------------------------- *
|
||||
* positive tests *
|
||||
* ----------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
public void Zip_Format_Should_Return_ZipArchiver()
|
||||
{
|
||||
// Arrange
|
||||
var registry = new ArchiverRegistry(new NullLogger<ArchiverRegistry>());
|
||||
|
||||
// Act
|
||||
var archiver = registry.Lookup("zip");
|
||||
|
||||
// Assert
|
||||
archiver.Should().NotBeNull();
|
||||
archiver.Should().BeOfType<ZipArchiver>();
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- *
|
||||
* negative tests *
|
||||
* ----------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
public void Unknown_Format_Should_Return_Null()
|
||||
{
|
||||
// Arrange
|
||||
var registry = new ArchiverRegistry(new NullLogger<ArchiverRegistry>());
|
||||
|
||||
// Act
|
||||
var archiver = registry.Lookup("unknown");
|
||||
|
||||
// Assert
|
||||
archiver.Should().BeNull();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,553 @@
|
|||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moq;
|
||||
using Steeltoe.Common.Utils.Diagnostics;
|
||||
using Steeltoe.NetCoreToolService.Controllers;
|
||||
using Steeltoe.NetCoreToolService.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Test.Controllers
|
||||
{
|
||||
public class NewControllerTest
|
||||
|
||||
{
|
||||
/* ----------------------------------------------------------------- *
|
||||
* positive tests *
|
||||
* ----------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplates_Should_Return_AllTemplates()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(expression: c => c.ExecuteAsync($"{NetCoreTool.Command} new --list", null, -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
------------------- -------- --------- ---------
|
||||
My Template myt lang tags
|
||||
My Other Template myot otherlang othertags
|
||||
",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplates();
|
||||
|
||||
// Assert
|
||||
var okResult = Assert.IsType<OkObjectResult>(result);
|
||||
var templates = Assert.IsType<TemplateDictionary>(okResult.Value);
|
||||
templates.Count.Should().Be(2);
|
||||
templates.Keys.Should().Contain("myt");
|
||||
templates["myt"].Name.Should().Be("My Template");
|
||||
templates["myt"].Languages.Should().Be("lang");
|
||||
templates["myt"].Tags.Should().Be("tags");
|
||||
templates.Keys.Should().Contain("myot");
|
||||
templates["myot"].Name.Should().Be("My Other Template");
|
||||
templates["myot"].Languages.Should().Be("otherlang");
|
||||
templates["myot"].Tags.Should().Be("othertags");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InstallTemplates_Should_Return_InstalledTemplates()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.SetupSequence(c => c.ExecuteAsync($"{NetCoreTool.Command} new --list", null, -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
------------------- -------- --------- ---------
|
||||
My Template myt lang tags
|
||||
My Other Template myot otherlang othertags
|
||||
",
|
||||
}
|
||||
)
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
------------------- -------- --------- ----------
|
||||
A New Template ant smalltalk newstuff
|
||||
My Template myt lang tags
|
||||
My Other Template myot otherlang othertags
|
||||
Other New Template ont bigtalk otherstuff
|
||||
",
|
||||
}
|
||||
);
|
||||
executor.Setup(c => c.ExecuteAsync($"{NetCoreTool.Command} new --install My.Templates", null, -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = "",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.InstallTemplates("My.Templates");
|
||||
|
||||
// Assert
|
||||
var createdResult = Assert.IsType<CreatedAtActionResult>(result);
|
||||
var templates = Assert.IsType<TemplateDictionary>(createdResult.Value);
|
||||
templates.Count.Should().Be(2);
|
||||
templates["ant"].Name.Should().Be("A New Template");
|
||||
templates["ant"].Languages.Should().Be("smalltalk");
|
||||
templates["ant"].Tags.Should().Be("newstuff");
|
||||
templates["ont"].Name.Should().Be("Other New Template");
|
||||
templates["ont"].Languages.Should().Be("bigtalk");
|
||||
templates["ont"].Tags.Should().Be("otherstuff");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UninstallTemplates_Should_Return_UninstalledTemplates()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.SetupSequence(c => c.ExecuteAsync($"{NetCoreTool.Command} new --list", null, -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
------------------- -------- --------- ----------
|
||||
A New Template ant smalltalk newstuff
|
||||
My Template myt lang tags
|
||||
My Other Template myot otherlang othertags
|
||||
Other New Template ont bigtalk otherstuff
|
||||
",
|
||||
}
|
||||
)
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
------------------- -------- --------- ---------
|
||||
My Template myt lang tags
|
||||
My Other Template myot otherlang othertags
|
||||
",
|
||||
}
|
||||
);
|
||||
executor.Setup(c => c.ExecuteAsync($"{NetCoreTool.Command} new --uninstall My.Templates", null, -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = "",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.UninstallTemplates("My.Templates");
|
||||
|
||||
// Assert
|
||||
var ok = Assert.IsType<OkObjectResult>(result);
|
||||
var templates = Assert.IsType<TemplateDictionary>(ok.Value);
|
||||
templates.Count.Should().Be(2);
|
||||
templates["ant"].Name.Should().Be("A New Template");
|
||||
templates["ant"].Languages.Should().Be("smalltalk");
|
||||
templates["ant"].Tags.Should().Be("newstuff");
|
||||
templates["ont"].Name.Should().Be("Other New Template");
|
||||
templates["ont"].Languages.Should().Be("bigtalk");
|
||||
templates["ont"].Tags.Should().Be("otherstuff");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateHelp_Should_Return_Help()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c => c.ExecuteAsync($"{NetCoreTool.Command} new mytemplate --help", null, -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
Some helpful tips for you
|
||||
",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateHelp("mytemplate");
|
||||
|
||||
// Assert
|
||||
var ok = Assert.IsType<OkObjectResult>(result);
|
||||
var help = Assert.IsType<string>(ok.Value);
|
||||
help.Should().Be("Some helpful tips for you");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateProject_Should_Return_ProjectPackage()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c => c.ExecuteAsync($"{NetCoreTool.Command} new mytemplate --output=Sample",
|
||||
It.IsAny<string>(), -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
The template ""mytemplate"" was created successfully.
|
||||
",
|
||||
Error = "",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateProject("mytemplate");
|
||||
|
||||
// Assert
|
||||
Assert.IsType<FileContentResult>(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateProject_Should_Use_Defaults()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c => c.ExecuteAsync($"{NetCoreTool.Command} new mytemplate --output=Sample",
|
||||
It.IsAny<string>(), -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
The template ""mytemplate"" was created successfully.
|
||||
",
|
||||
Error = "",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateProject("mytemplate");
|
||||
|
||||
// Assert
|
||||
var file = Assert.IsType<FileContentResult>(result);
|
||||
file.ContentType.Should().Be("application/zip");
|
||||
file.FileDownloadName.Should().Be("Sample.zip");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateProject_Can_Specify_Output()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c => c.ExecuteAsync($"{NetCoreTool.Command} new mytemplate --output=Joe",
|
||||
It.IsAny<string>(), -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
The template ""mytemplate"" was created successfully.
|
||||
",
|
||||
Error = "",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateProject("mytemplate", "output=Joe");
|
||||
|
||||
// Assert
|
||||
var file = Assert.IsType<FileContentResult>(result);
|
||||
file.FileDownloadName.Should().StartWith("Joe.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateProject_Can_Specify_ZipPackaging()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c => c.ExecuteAsync($"{NetCoreTool.Command} new mytemplate --output=Sample",
|
||||
It.IsAny<string>(), -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
The template ""mytemplate"" was created successfully.
|
||||
",
|
||||
Error = "",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateProject("mytemplate", packaging: "zip");
|
||||
|
||||
// Assert
|
||||
var file = Assert.IsType<FileContentResult>(result);
|
||||
var _ = new ZipArchive(new MemoryStream(file.FileContents));
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- *
|
||||
* negative tests *
|
||||
* ----------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
public async Task InstallTemplates_UnknownNuGet_Should_Return_BadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c => c.ExecuteAsync($"{NetCoreTool.Command} new --list", null, -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
------------------- -------- --------- ---------
|
||||
My Template myt lang tags
|
||||
My Other Template myot otherlang othertags
|
||||
",
|
||||
}
|
||||
);
|
||||
executor.Setup(c =>
|
||||
c.ExecuteAsync($"{NetCoreTool.Command} new --install No.Such.Template", null, -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 2,
|
||||
Output = @"
|
||||
... error NU1101: Unable to find package No.Such.Template. No packages exist with this id in source(s): myget.org, nuget.org
|
||||
Failed to restore ...
|
||||
",
|
||||
});
|
||||
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.InstallTemplates("No.Such.Template");
|
||||
|
||||
// Assert
|
||||
var badRequest = Assert.IsType<BadRequestObjectResult>(result);
|
||||
badRequest.Value.Should()
|
||||
.Be(
|
||||
"Unable to find package No.Such.Template. No packages exist with this id in source(s): myget.org, nuget.org");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UninstallTemplates_UnknownNuGet_Should_Return_NotFound()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c => c.ExecuteAsync($"{NetCoreTool.Command} new --list", null, -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
------------------- -------- --------- ---------
|
||||
",
|
||||
}
|
||||
);
|
||||
executor.Setup(c => c.ExecuteAsync($"{NetCoreTool.Command} new --uninstall My.Templates", null, -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
Could not find something to uninstall called 'My.Templates'.
|
||||
",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.UninstallTemplates("My.Templates");
|
||||
|
||||
// Assert
|
||||
var notFound = Assert.IsType<NotFoundObjectResult>(result);
|
||||
notFound.Value.Should().Be("No templates with NuGet ID 'My.Templates' installed.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateHelp_UnknownTemplate_Should_Return_NotFound()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c => c.ExecuteAsync($"{NetCoreTool.Command} new nosuchtemplate --help", null, -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 6,
|
||||
Error = @"
|
||||
No templates found matching: 'nosuchtemplate'.
|
||||
",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateHelp("nosuchtemplate");
|
||||
|
||||
// Assert
|
||||
var notFound = Assert.IsType<NotFoundObjectResult>(result);
|
||||
notFound.Value.Should().Be("No templates found matching: 'nosuchtemplate'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateProject_UnknownTemplate_Should_Return_NotFound()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c =>
|
||||
c.ExecuteAsync($"{NetCoreTool.Command} new nosuchtemplate --output=Sample", It.IsAny<string>(), -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 14,
|
||||
Error = @"
|
||||
No templates found matching: 'nosuchtemplate'.
|
||||
",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateProject("nosuchtemplate");
|
||||
|
||||
// Assert
|
||||
var notFound = Assert.IsType<NotFoundObjectResult>(result);
|
||||
notFound.Value.Should().Be("Template 'nosuchtemplate' not found.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateProject_UnknownSwitch_Should_Return_NotFound()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c =>
|
||||
c.ExecuteAsync($"{NetCoreTool.Command} new mytemplate --output=Sample --unknown-switch",
|
||||
It.IsAny<string>(), -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 5,
|
||||
Error = @"
|
||||
Invalid input switch:
|
||||
--unknown-switch
|
||||
For a list of valid options, run 'dotnet new webapi --help'.
|
||||
",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateProject("mytemplate", "unknown-switch");
|
||||
|
||||
// Assert
|
||||
var notFound = Assert.IsType<NotFoundObjectResult>(result);
|
||||
notFound.Value.Should().Be("Switch 'unknown-switch' not found.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateProject_UnknownParameter_Should_Return_NotFound()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c =>
|
||||
c.ExecuteAsync($"{NetCoreTool.Command} new mytemplate --output=Sample --myoption=unknown",
|
||||
It.IsAny<string>(), -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = "",
|
||||
Error = @"
|
||||
Error: Invalid parameter(s):
|
||||
--myoption unknown
|
||||
'unknown' is not a valid value for --myoption
|
||||
",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateProject("mytemplate", "myoption=unknown");
|
||||
|
||||
// Assert
|
||||
var notFound = Assert.IsType<NotFoundObjectResult>(result);
|
||||
notFound.Value.Should().Be("Option 'myoption' parameter 'unknown' not found.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateProject_UnknownPackaging_Should_Return_BadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c =>
|
||||
c.ExecuteAsync($"{NetCoreTool.Command} new mytemplate --output=Sample --myoption=unknown",
|
||||
It.IsAny<string>(), -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = "",
|
||||
Error = @"
|
||||
Error: Invalid parameter(s):
|
||||
--myoption unknown
|
||||
'unknown' is not a valid value for --myoption
|
||||
",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateProject("mytemplate", packaging: "acme-packaging");
|
||||
|
||||
// Assert
|
||||
var badRequest = Assert.IsType<BadRequestObjectResult>(result);
|
||||
badRequest.Value.Should().Be("Unknown or unsupported packaging 'acme-packaging'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateProject_UnknownError_Should_Return_InternalServerError()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c => c.ExecuteAsync(It.IsAny<string>(), It.IsAny<string>(), -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 1,
|
||||
Output = "",
|
||||
Error = @"
|
||||
Something bad happened.
|
||||
",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateProject("mytemplate");
|
||||
|
||||
// Assert
|
||||
var internalServerError = Assert.IsType<ObjectResult>(result);
|
||||
internalServerError.StatusCode.Should().Be(StatusCodes.Status500InternalServerError);
|
||||
internalServerError.Value.Should().Be("Something bad happened.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTemplateProject_UnknownOutput_Should_Return_InternalServerError()
|
||||
{
|
||||
// Arrange
|
||||
var executor = new Mock<ICommandExecutor>();
|
||||
executor.Setup(c => c.ExecuteAsync(It.IsAny<string>(), It.IsAny<string>(), -1))
|
||||
.ReturnsAsync(new CommandResult
|
||||
{
|
||||
ExitCode = 0,
|
||||
Output = @"
|
||||
Unexpected output.
|
||||
",
|
||||
Error = "",
|
||||
}
|
||||
);
|
||||
var controller = new NewController(executor.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetTemplateProject("mytemplate");
|
||||
|
||||
// Assert
|
||||
var internalServerError = Assert.IsType<ObjectResult>(result);
|
||||
internalServerError.StatusCode.Should().Be(StatusCodes.Status500InternalServerError);
|
||||
internalServerError.Value.Should().Be("Unexpected output.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,13 +5,13 @@
|
|||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using FluentAssertions;
|
||||
using Steeltoe.NetCoreToolService.Archivers;
|
||||
using Steeltoe.NetCoreToolService.Utils.IO;
|
||||
using Steeltoe.Common.Utils.IO;
|
||||
using Steeltoe.NetCoreToolService.Packagers;
|
||||
using Xunit;
|
||||
|
||||
namespace Steeltoe.NetCoreToolService.Test.Archivers
|
||||
namespace Steeltoe.NetCoreToolService.Test.Packagers
|
||||
{
|
||||
public class ZipArchiverTests
|
||||
public class ZipPackagerTests
|
||||
{
|
||||
/* ----------------------------------------------------------------- *
|
||||
* positive tests *
|
||||
|
@ -21,11 +21,11 @@ namespace Steeltoe.NetCoreToolService.Test.Archivers
|
|||
public void ToStream_Should_Create_Zip_Archive()
|
||||
{
|
||||
// Arrange
|
||||
var archiver = new ZipArchiver();
|
||||
var archiver = new ZipPackager();
|
||||
var tempDir = new TempDirectory();
|
||||
|
||||
// Act
|
||||
var buf = archiver.ToBytes(tempDir.FullName);
|
||||
var buf = archiver.ToBytes(tempDir.FullPath);
|
||||
|
||||
// Assert
|
||||
new ZipArchive(new MemoryStream(buf)).Should().BeOfType<ZipArchive>();
|
||||
|
@ -35,15 +35,15 @@ namespace Steeltoe.NetCoreToolService.Test.Archivers
|
|||
public void ToStream_Should_Archive_File_Contents()
|
||||
{
|
||||
// Arrange
|
||||
var archiver = new ZipArchiver();
|
||||
var archiver = new ZipPackager();
|
||||
using var tempDir = new TempDirectory();
|
||||
var d1 = Path.Join(tempDir.FullName, "d1");
|
||||
var d1 = Path.Join(tempDir.FullPath, "d1");
|
||||
Directory.CreateDirectory(d1);
|
||||
var f1 = Path.Join(d1, "f1");
|
||||
File.WriteAllText(f1, "f1 stuff");
|
||||
|
||||
// Act
|
||||
var buf = archiver.ToBytes(tempDir.FullName);
|
||||
var buf = archiver.ToBytes(tempDir.FullPath);
|
||||
|
||||
// Assert
|
||||
var zip = new ZipArchive(new MemoryStream(buf));
|
||||
|
@ -64,15 +64,15 @@ namespace Steeltoe.NetCoreToolService.Test.Archivers
|
|||
public void ToStream_Should_Archive_Directories()
|
||||
{
|
||||
// Arrange
|
||||
var archiver = new ZipArchiver();
|
||||
var archiver = new ZipPackager();
|
||||
using var tempDir = new TempDirectory();
|
||||
var d1 = Path.Join(tempDir.FullName, "d1");
|
||||
var d1 = Path.Join(tempDir.FullPath, "d1");
|
||||
Directory.CreateDirectory(d1);
|
||||
var d2 = Path.Join(d1, "d2");
|
||||
Directory.CreateDirectory(d2);
|
||||
|
||||
// Act
|
||||
var buf = archiver.ToBytes(tempDir.FullName);
|
||||
var buf = archiver.ToBytes(tempDir.FullPath);
|
||||
|
||||
// Assert
|
||||
var zip = new ZipArchive(new MemoryStream(buf));
|
||||
|
@ -92,7 +92,7 @@ namespace Steeltoe.NetCoreToolService.Test.Archivers
|
|||
public void GetPackaging_Should_Be_application_zip()
|
||||
{
|
||||
// Arrange
|
||||
var archiver = new ZipArchiver();
|
||||
var archiver = new ZipPackager();
|
||||
|
||||
// Act
|
||||
var packaging = archiver.Name;
|
||||
|
@ -105,7 +105,7 @@ namespace Steeltoe.NetCoreToolService.Test.Archivers
|
|||
public void GetFileExtension_Should_Be_zip()
|
||||
{
|
||||
// Arrange
|
||||
var archiver = new ZipArchiver();
|
||||
var archiver = new ZipPackager();
|
||||
|
||||
// Act
|
||||
var ext = archiver.FileExtension;
|
||||
|
@ -118,7 +118,7 @@ namespace Steeltoe.NetCoreToolService.Test.Archivers
|
|||
public void MimeType_Should_Be_application_zip()
|
||||
{
|
||||
// Arrange
|
||||
var archiver = new ZipArchiver();
|
||||
var archiver = new ZipPackager();
|
||||
|
||||
// Act
|
||||
var ext = archiver.MimeType;
|
|
@ -1,9 +1,5 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Steeltoe.NetCoreToolService.Test</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\Directory.Build.props">
|
||||
<Link>Directory.Build.props</Link>
|
||||
|
@ -11,7 +7,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\NetCoreToolService\NetCoreToolService.csproj" />
|
||||
<ProjectReference Include="..\..\src\NetCoreToolService\Steeltoe.NetCoreToolService.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Загрузка…
Ссылка в новой задаче