Skip to content

Commit 6b253cf

Browse files
committed
Add unit tests for new controller
1 parent 939a4eb commit 6b253cf

34 files changed

+1303
-420
lines changed

.editorconfig

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
trim_trailing_whitespace = true
6+
insert_final_newline = true
7+
max_line_length = 120
8+
charset = utf-8
9+
10+
[*.{csproj,props,targets,DotSettings}]
11+
indent_size = 2
12+
13+
[*.cs]
14+
indent_size = 4
15+
16+
[*.yaml]
17+
indent_size = 2
18+
19+
[*.ps1]
20+
indent_size = 4
21+
22+
[*.json]
23+
indent_size = 2

NetCoreToolService.sln renamed to Steeltoe.NetCoreToolService.sln

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ VisualStudioVersion = 16.6.30114.105
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C742A7B8-80CA-4365-85CA-C29AA744CE54}"
77
EndProject
8-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCoreToolService", "src\NetCoreToolService\NetCoreToolService.csproj", "{1462EDFE-F1FC-48C2-80C1-917317EE3C97}"
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Common.Utils", "src\Common.Utils\Steeltoe.Common.Utils.csproj", "{3E82184B-FA14-4B24-9ED0-A823465B6AB9}"
9+
EndProject
10+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.NetCoreToolService", "src\NetCoreToolService\Steeltoe.NetCoreToolService.csproj", "{1462EDFE-F1FC-48C2-80C1-917317EE3C97}"
911
EndProject
1012
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{410C0E72-737F-4168-AECA-2F6D19EE86D5}"
1113
EndProject
12-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCoreToolService.Test", "test\NetCoreToolService.Test\NetCoreToolService.Test.csproj", "{6BD6C793-E555-475F-A2E1-12D7F3F56A3B}"
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Common.Utils.Test", "test\Common.Utils.Test\Steeltoe.Common.Utils.Test.csproj", "{06247239-19D8-4FB5-99F1-F399346ADE4F}"
15+
EndProject
16+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.NetCoreToolService.Test", "test\NetCoreToolService.Test\Steeltoe.NetCoreToolService.Test.csproj", "{6BD6C793-E555-475F-A2E1-12D7F3F56A3B}"
1317
EndProject
1418
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C37528B8-BDD1-440F-B69A-AD57F939620C}"
1519
ProjectSection(SolutionItems) = preProject
20+
.editorconfig = .editorconfig
1621
.gitconfig = .gitconfig
1722
.gitignore = .gitignore
1823
Directory.Build.props = Directory.Build.props
@@ -57,13 +62,39 @@ Global
5762
{6BD6C793-E555-475F-A2E1-12D7F3F56A3B}.Release|x64.Build.0 = Release|Any CPU
5863
{6BD6C793-E555-475F-A2E1-12D7F3F56A3B}.Release|x86.ActiveCfg = Release|Any CPU
5964
{6BD6C793-E555-475F-A2E1-12D7F3F56A3B}.Release|x86.Build.0 = Release|Any CPU
65+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
67+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|x64.ActiveCfg = Debug|Any CPU
68+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|x64.Build.0 = Debug|Any CPU
69+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|x86.ActiveCfg = Debug|Any CPU
70+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Debug|x86.Build.0 = Debug|Any CPU
71+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
72+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|Any CPU.Build.0 = Release|Any CPU
73+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|x64.ActiveCfg = Release|Any CPU
74+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|x64.Build.0 = Release|Any CPU
75+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|x86.ActiveCfg = Release|Any CPU
76+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9}.Release|x86.Build.0 = Release|Any CPU
77+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
78+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
79+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|x64.ActiveCfg = Debug|Any CPU
80+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|x64.Build.0 = Debug|Any CPU
81+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|x86.ActiveCfg = Debug|Any CPU
82+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Debug|x86.Build.0 = Debug|Any CPU
83+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
84+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|Any CPU.Build.0 = Release|Any CPU
85+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|x64.ActiveCfg = Release|Any CPU
86+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|x64.Build.0 = Release|Any CPU
87+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|x86.ActiveCfg = Release|Any CPU
88+
{06247239-19D8-4FB5-99F1-F399346ADE4F}.Release|x86.Build.0 = Release|Any CPU
6089
EndGlobalSection
6190
GlobalSection(SolutionProperties) = preSolution
6291
HideSolutionNode = FALSE
6392
EndGlobalSection
6493
GlobalSection(NestedProjects) = preSolution
6594
{1462EDFE-F1FC-48C2-80C1-917317EE3C97} = {C742A7B8-80CA-4365-85CA-C29AA744CE54}
6695
{6BD6C793-E555-475F-A2E1-12D7F3F56A3B} = {410C0E72-737F-4168-AECA-2F6D19EE86D5}
96+
{3E82184B-FA14-4B24-9ED0-A823465B6AB9} = {C742A7B8-80CA-4365-85CA-C29AA744CE54}
97+
{06247239-19D8-4FB5-99F1-F399346ADE4F} = {410C0E72-737F-4168-AECA-2F6D19EE86D5}
6798
EndGlobalSection
6899
GlobalSection(ExtensibilityGlobals) = postSolution
69100
SolutionGuid = {D8EFB01A-92BF-418B-B2B2-A12045B772E2}

Version.props

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,22 @@
22

33
<PropertyGroup>
44
<SteeltoeNetCoreToolServiceVersion>0.0.1</SteeltoeNetCoreToolServiceVersion>
5-
<SwashbuckleVersion>5.6.*</SwashbuckleVersion>
5+
</PropertyGroup>
6+
7+
<PropertyGroup>
8+
<MicrosoftExtensionsVersion>5.0.*</MicrosoftExtensionsVersion>
9+
<SwashbuckleVersion>5.6.*</SwashbuckleVersion>
10+
</PropertyGroup>
11+
12+
<PropertyGroup>
13+
<CoverletCollectorVersion>1.3.*</CoverletCollectorVersion>
14+
<CoverletMsBuildVersion>2.9.*</CoverletMsBuildVersion>
15+
<FluentAssertionsVersion>5.10.*</FluentAssertionsVersion>
16+
<FluentAssertionsJsonVersion>5.5.*</FluentAssertionsJsonVersion>
17+
<MicrosoftAspNetCoreMvcTestingVersion>3.1.*</MicrosoftAspNetCoreMvcTestingVersion>
18+
<MicrosoftNetTestSdkVersion>16.7.*</MicrosoftNetTestSdkVersion>
19+
<MoqVersion>4.14.*</MoqVersion>
20+
<XunitVersion>2.4.*</XunitVersion>
621
</PropertyGroup>
722

823
</Project>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
7+
namespace Steeltoe.Common.Utils.Diagnostics
8+
{
9+
/// <summary>
10+
/// The exception that is thrown when a system error occurs running a command.
11+
/// </summary>
12+
public class CommandException : Exception
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="CommandException"/> class.
16+
/// </summary>
17+
/// <param name="message">Foo.</param>
18+
/// <param name="innerException">Foo bar.</param>
19+
public CommandException(string message, Exception innerException)
20+
: base(message, innerException)
21+
{
22+
}
23+
}
24+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.Extensions.Logging;
6+
using System;
7+
using System.Diagnostics;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
11+
namespace Steeltoe.Common.Utils.Diagnostics
12+
{
13+
/// <inheritdoc/>
14+
public class CommandExecutor : ICommandExecutor
15+
{
16+
private static int _commandCounter;
17+
18+
private readonly ILogger<CommandExecutor> _logger;
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="CommandExecutor"/> class.
22+
/// </summary>
23+
/// <param name="logger">Injected logger.</param>
24+
public CommandExecutor(ILogger<CommandExecutor> logger = null)
25+
{
26+
_logger = logger;
27+
}
28+
29+
/// <inheritdoc/>
30+
public async Task<CommandResult> ExecuteAsync(string command, string workingDirectory = null, int timeout = -1)
31+
{
32+
var commandId = ++_commandCounter;
33+
using var process = new Process();
34+
var arguments = command.Split(new[] { ' ' }, 2);
35+
process.StartInfo.FileName = arguments[0];
36+
if (arguments.Length > 1)
37+
{
38+
process.StartInfo.Arguments = arguments[1];
39+
}
40+
41+
if (workingDirectory != null)
42+
{
43+
process.StartInfo.WorkingDirectory = workingDirectory;
44+
}
45+
46+
process.StartInfo.RedirectStandardInput = true;
47+
process.StartInfo.RedirectStandardOutput = true;
48+
process.StartInfo.RedirectStandardError = true;
49+
process.StartInfo.UseShellExecute = false;
50+
process.StartInfo.CreateNoWindow = true;
51+
52+
var output = new StringBuilder();
53+
var outputCloseEvent = new TaskCompletionSource<bool>();
54+
process.OutputDataReceived += (_, e) =>
55+
{
56+
if (e.Data is null)
57+
{
58+
outputCloseEvent.SetResult(true);
59+
}
60+
else
61+
{
62+
output.AppendLine(e.Data);
63+
}
64+
};
65+
66+
var error = new StringBuilder();
67+
var errorCloseEvent = new TaskCompletionSource<bool>();
68+
process.ErrorDataReceived += (_, e) =>
69+
{
70+
if (e.Data is null)
71+
{
72+
errorCloseEvent.SetResult(true);
73+
}
74+
else
75+
{
76+
error.AppendLine(e.Data);
77+
}
78+
};
79+
80+
_logger?.LogDebug("[{CommandId}] command: {Command}", commandId, command);
81+
try
82+
{
83+
if (!process.Start())
84+
{
85+
_logger?.LogDebug("[{CommandId}] failed to start: {Error}", commandId, "no details available");
86+
throw new Exception($"'{command}' failed to start; no details available");
87+
}
88+
}
89+
catch (Exception ex)
90+
{
91+
_logger?.LogDebug("[{CommandId}] failed to start: {Error}", commandId, ex.Message);
92+
throw new CommandException($"'{command}' failed to start: {ex.Message}", ex);
93+
}
94+
95+
process.BeginOutputReadLine();
96+
process.BeginErrorReadLine();
97+
98+
// ReSharper disable once AccessToDisposedClosure
99+
var waitForExit = Task.Run(() => process.WaitForExit(timeout));
100+
var processTask = Task.WhenAll(waitForExit, outputCloseEvent.Task, errorCloseEvent.Task);
101+
if (await Task.WhenAny(Task.Delay(timeout), processTask) == processTask && waitForExit.Result)
102+
{
103+
var result = new CommandResult
104+
{
105+
ExitCode = process.ExitCode, Output = output.ToString(), Error = error.ToString(),
106+
};
107+
_logger?.LogDebug("[{CommandId}] exit code: {ExitCode}", commandId, result.ExitCode);
108+
if (result.Output.Length > 0)
109+
{
110+
_logger?.LogDebug("[{CommandId}] stdout:\n{Output}", commandId, result.Output);
111+
}
112+
113+
if (result.Error.Length > 0)
114+
{
115+
_logger?.LogDebug("[{CommandId}] stderr:\n{Error}", commandId, result.Error);
116+
}
117+
118+
return result;
119+
}
120+
121+
try
122+
{
123+
process.Kill();
124+
}
125+
catch
126+
{
127+
// ignore
128+
}
129+
130+
_logger?.LogDebug("[{CommandId}] timed out: {TimeOut}ms", commandId, timeout);
131+
throw new Exception($"'{process.StartInfo.FileName} {process.StartInfo.Arguments}' timed out");
132+
}
133+
}
134+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace Steeltoe.Common.Utils.Diagnostics
6+
{
7+
/// <summary>
8+
/// An simple abstraction of a command result.
9+
/// </summary>
10+
public struct CommandResult
11+
{
12+
/// <summary>
13+
/// Gets the command exit code.
14+
/// </summary>
15+
public int ExitCode { get; init; }
16+
17+
/// <summary>
18+
/// Gets the command exit STDOUT.
19+
/// </summary>
20+
public string Output { get; init; }
21+
22+
/// <summary>
23+
/// Gets the command exit STDERR.
24+
/// </summary>
25+
public string Error { get; init; }
26+
}
27+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Threading.Tasks;
6+
7+
namespace Steeltoe.Common.Utils.Diagnostics
8+
{
9+
/// <summary>
10+
/// A utility abstraction to simplify the running of commands.
11+
/// </summary>
12+
public interface ICommandExecutor
13+
{
14+
/// <summary>
15+
/// Execute the command and return the result.
16+
/// </summary>
17+
/// <param name="command">Command to be executed.</param>
18+
/// <param name="workingDirectory">The directory that contains the command process.</param>
19+
/// <param name="timeout">The amount of time in milliseconds to wait for command to complete.</param>
20+
/// <returns>Command result.</returns>
21+
/// <exception cref="CommandException">If a process can not be started for command.</exception>
22+
Task<CommandResult> ExecuteAsync(string command, string workingDirectory = null, int timeout = -1);
23+
}
24+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
</Project>

src/NetCoreToolService/Utils/IO/TempDirectory.cs renamed to src/Common.Utils/IO/TempDirectory.cs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,42 @@
44

55
using System.IO;
66

7-
namespace Steeltoe.NetCoreToolService.Utils.IO
7+
namespace Steeltoe.Common.Utils.IO
88
{
99
/// <summary>
10-
/// A temp directory.
10+
/// A temporary directory.
1111
/// </summary>
12-
public sealed class TempDirectory : TempPath
12+
public class TempDirectory : TempPath
1313
{
1414
/// <summary>
1515
/// Initializes a new instance of the <see cref="TempDirectory"/> class.
1616
/// </summary>
17-
public TempDirectory()
18-
: this(true)
17+
/// <param name="prefix">Temporary directory prefix.</param>
18+
public TempDirectory(string prefix = null)
19+
: base(prefix)
1920
{
2021
}
2122

2223
/// <summary>
23-
/// Initializes a new instance of the <see cref="TempDirectory"/> class.
24+
/// Creates the temporary directory.
2425
/// </summary>
25-
/// <param name="create">If true, create the directory.</param>
26-
public TempDirectory(bool create)
26+
protected override void InitializePath()
2727
{
28-
if (create)
29-
{
30-
Directory.CreateDirectory(FullName);
31-
}
28+
Directory.CreateDirectory(FullPath);
3229
}
3330

3431
/// <inheritdoc/>
3532
protected override void Dispose(bool disposing)
3633
{
3734
base.Dispose(disposing);
38-
if (!Directory.Exists(FullName))
35+
if (!Directory.Exists(FullPath))
3936
{
4037
return;
4138
}
4239

4340
try
4441
{
45-
Directory.Delete(FullName, true);
42+
Directory.Delete(FullPath, true);
4643
}
4744
catch
4845
{

0 commit comments

Comments
 (0)