|
| 1 | +using System.Runtime.InteropServices; |
| 2 | +using System.Text.Json; |
| 3 | +using Microsoft.VisualStudio.TestTools.UnitTesting; |
| 4 | + |
| 5 | +namespace DemaConsulting.DotnetToolWrapper.Tests; |
| 6 | + |
| 7 | +/// <summary> |
| 8 | +/// Integration tests for the DotnetToolWrapper application |
| 9 | +/// </summary> |
| 10 | +[TestClass] |
| 11 | +public class IntegrationTests |
| 12 | +{ |
| 13 | + /// <summary> |
| 14 | + /// Test setup directory |
| 15 | + /// </summary> |
| 16 | + private string _testDirectory = string.Empty; |
| 17 | + |
| 18 | + /// <summary> |
| 19 | + /// Initialize test |
| 20 | + /// </summary> |
| 21 | + [TestInitialize] |
| 22 | + public void TestInitialize() |
| 23 | + { |
| 24 | + // Create a unique test directory |
| 25 | + _testDirectory = Path.Combine(Path.GetTempPath(), $"DotnetToolWrapperTests_{Guid.NewGuid()}"); |
| 26 | + Directory.CreateDirectory(_testDirectory); |
| 27 | + } |
| 28 | + |
| 29 | + /// <summary> |
| 30 | + /// Cleanup test |
| 31 | + /// </summary> |
| 32 | + [TestCleanup] |
| 33 | + public void TestCleanup() |
| 34 | + { |
| 35 | + // Clean up test directory |
| 36 | + if (Directory.Exists(_testDirectory)) |
| 37 | + { |
| 38 | + Directory.Delete(_testDirectory, true); |
| 39 | + } |
| 40 | + |
| 41 | + // Clean up config file next to DLL |
| 42 | + var dllPath = GetDotnetToolWrapperDllPath(); |
| 43 | + var dllDirectory = Path.GetDirectoryName(dllPath); |
| 44 | + if (dllDirectory != null) |
| 45 | + { |
| 46 | + var configPath = Path.Combine(dllDirectory, "DotnetToolWrapper.json"); |
| 47 | + if (File.Exists(configPath)) |
| 48 | + { |
| 49 | + File.Delete(configPath); |
| 50 | + } |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + /// <summary> |
| 55 | + /// Get the path to the DotnetToolWrapper DLL |
| 56 | + /// </summary> |
| 57 | + /// <returns>Path to DLL</returns> |
| 58 | + private static string GetDotnetToolWrapperDllPath() |
| 59 | + { |
| 60 | + var assemblyLocation = Path.GetDirectoryName(typeof(IntegrationTests).Assembly.Location); |
| 61 | + Assert.IsNotNull(assemblyLocation, "Assembly location should not be null"); |
| 62 | + return Path.Combine(assemblyLocation, "DemaConsulting.DotnetToolWrapper.dll"); |
| 63 | + } |
| 64 | + |
| 65 | + /// <summary> |
| 66 | + /// Create a DotnetToolWrapper.json configuration file |
| 67 | + /// </summary> |
| 68 | + /// <param name="program">Program to execute</param> |
| 69 | + /// <param name="directory">Directory for config file (defaults to DLL directory)</param> |
| 70 | + private static void CreateConfigFile(string program, string? directory = null) |
| 71 | + { |
| 72 | + // Default to DLL directory |
| 73 | + if (directory == null) |
| 74 | + { |
| 75 | + var dllPath = GetDotnetToolWrapperDllPath(); |
| 76 | + directory = Path.GetDirectoryName(dllPath); |
| 77 | + Assert.IsNotNull(directory, "DLL directory should not be null"); |
| 78 | + } |
| 79 | + |
| 80 | + var target = Program.GetTarget(); |
| 81 | + |
| 82 | + var json = JsonSerializer.Serialize(new Dictionary<string, object> |
| 83 | + { |
| 84 | + { target, new { program } } |
| 85 | + }); |
| 86 | + |
| 87 | + File.WriteAllText(Path.Combine(directory, "DotnetToolWrapper.json"), json); |
| 88 | + } |
| 89 | + |
| 90 | + /// <summary> |
| 91 | + /// Get the shell program name for the current OS |
| 92 | + /// </summary> |
| 93 | + /// <returns>Shell program name</returns> |
| 94 | + private static string GetShellProgram() |
| 95 | + { |
| 96 | + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
| 97 | + { |
| 98 | + // Use COMSPEC environment variable to get full path to cmd.exe |
| 99 | + var comspec = Environment.GetEnvironmentVariable("COMSPEC"); |
| 100 | + return comspec ?? throw new InvalidOperationException("COMSPEC environment variable not found"); |
| 101 | + } |
| 102 | + |
| 103 | + return "/bin/sh"; |
| 104 | + } |
| 105 | + |
| 106 | + /// <summary> |
| 107 | + /// Get shell arguments for exit code test |
| 108 | + /// </summary> |
| 109 | + /// <param name="exitCode">Exit code to test</param> |
| 110 | + /// <returns>Shell arguments</returns> |
| 111 | + private static string[] GetExitCodeArgs(int exitCode) |
| 112 | + { |
| 113 | + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) |
| 114 | + ? ["/c", $"exit {exitCode}"] |
| 115 | + : ["-c", $"exit {exitCode}"]; |
| 116 | + } |
| 117 | + |
| 118 | + /// <summary> |
| 119 | + /// Get shell arguments for echo test |
| 120 | + /// </summary> |
| 121 | + /// <param name="text">Text to echo</param> |
| 122 | + /// <returns>Shell arguments</returns> |
| 123 | + private static string[] GetEchoArgs(string text) |
| 124 | + { |
| 125 | + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) |
| 126 | + ? ["/c", $"echo {text}"] |
| 127 | + : ["-c", $"echo {text}"]; |
| 128 | + } |
| 129 | + |
| 130 | + /// <summary> |
| 131 | + /// Test that missing configuration file results in expected error |
| 132 | + /// </summary> |
| 133 | + [TestMethod] |
| 134 | + public void TestMissingConfigFile() |
| 135 | + { |
| 136 | + // Arrange |
| 137 | + var dllPath = GetDotnetToolWrapperDllPath(); |
| 138 | + |
| 139 | + // Act |
| 140 | + var exitCode = Runner.Run(out var output, "dotnet", dllPath); |
| 141 | + |
| 142 | + // Assert |
| 143 | + Assert.AreEqual(1, exitCode, "Exit code should be 1 for missing config file"); |
| 144 | + Assert.IsTrue(output.Contains("Missing configuration file"), "Output should mention missing config file"); |
| 145 | + Assert.IsTrue(output.Contains("DotnetToolWrapper.json"), "Output should mention config file name"); |
| 146 | + } |
| 147 | + |
| 148 | + /// <summary> |
| 149 | + /// Test that exit codes are properly passed through |
| 150 | + /// </summary> |
| 151 | + [TestMethod] |
| 152 | + [DataRow(0)] |
| 153 | + [DataRow(1)] |
| 154 | + [DataRow(42)] |
| 155 | + [DataRow(255)] |
| 156 | + public void TestExitCodes(int expectedExitCode) |
| 157 | + { |
| 158 | + // Arrange |
| 159 | + var dllPath = GetDotnetToolWrapperDllPath(); |
| 160 | + var shellProgram = GetShellProgram(); |
| 161 | + |
| 162 | + // Create config file pointing to shell |
| 163 | + CreateConfigFile(shellProgram); |
| 164 | + |
| 165 | + // Get shell arguments to exit with specific code |
| 166 | + var shellArgs = GetExitCodeArgs(expectedExitCode); |
| 167 | + |
| 168 | + // Prepare arguments for dotnet command |
| 169 | + var args = new List<string> { dllPath }; |
| 170 | + args.AddRange(shellArgs); |
| 171 | + |
| 172 | + // Act |
| 173 | + var exitCode = Runner.Run(out _, "dotnet", args.ToArray()); |
| 174 | + |
| 175 | + // Assert |
| 176 | + Assert.AreEqual(expectedExitCode, exitCode, $"Exit code should be {expectedExitCode}"); |
| 177 | + } |
| 178 | + |
| 179 | + /// <summary> |
| 180 | + /// Test that arguments are properly passed through |
| 181 | + /// </summary> |
| 182 | + [TestMethod] |
| 183 | + public void TestArgumentPassing() |
| 184 | + { |
| 185 | + // Arrange |
| 186 | + var dllPath = GetDotnetToolWrapperDllPath(); |
| 187 | + var shellProgram = GetShellProgram(); |
| 188 | + var testText = "HelloWorld"; |
| 189 | + |
| 190 | + // Create config file pointing to shell |
| 191 | + CreateConfigFile(shellProgram); |
| 192 | + |
| 193 | + // Get shell arguments to echo text |
| 194 | + var shellArgs = GetEchoArgs(testText); |
| 195 | + |
| 196 | + // Prepare arguments for dotnet command |
| 197 | + var args = new List<string> { dllPath }; |
| 198 | + args.AddRange(shellArgs); |
| 199 | + |
| 200 | + // Act |
| 201 | + var exitCode = Runner.Run(out var output, "dotnet", args.ToArray()); |
| 202 | + |
| 203 | + // Assert |
| 204 | + Assert.AreEqual(0, exitCode, "Exit code should be 0"); |
| 205 | + Assert.IsTrue(output.Contains(testText), $"Output should contain '{testText}'"); |
| 206 | + } |
| 207 | + |
| 208 | + /// <summary> |
| 209 | + /// Test that unsupported target results in expected error |
| 210 | + /// </summary> |
| 211 | + [TestMethod] |
| 212 | + public void TestUnsupportedTarget() |
| 213 | + { |
| 214 | + // Arrange |
| 215 | + var dllPath = GetDotnetToolWrapperDllPath(); |
| 216 | + var dllDirectory = Path.GetDirectoryName(dllPath); |
| 217 | + Assert.IsNotNull(dllDirectory, "DLL directory should not be null"); |
| 218 | + |
| 219 | + // Create config file with fake target |
| 220 | + var json = JsonSerializer.Serialize(new Dictionary<string, object> |
| 221 | + { |
| 222 | + { "fake-target", new { program = "fake" } } |
| 223 | + }); |
| 224 | + File.WriteAllText(Path.Combine(dllDirectory, "DotnetToolWrapper.json"), json); |
| 225 | + |
| 226 | + // Act |
| 227 | + var exitCode = Runner.Run(out var output, "dotnet", dllPath); |
| 228 | + |
| 229 | + // Assert |
| 230 | + Assert.AreEqual(1, exitCode, "Exit code should be 1 for unsupported target"); |
| 231 | + Assert.IsTrue(output.Contains("does not support"), "Output should mention unsupported target"); |
| 232 | + } |
| 233 | + |
| 234 | + /// <summary> |
| 235 | + /// Test that bad configuration results in expected error |
| 236 | + /// </summary> |
| 237 | + [TestMethod] |
| 238 | + public void TestBadConfiguration() |
| 239 | + { |
| 240 | + // Arrange |
| 241 | + var dllPath = GetDotnetToolWrapperDllPath(); |
| 242 | + var dllDirectory = Path.GetDirectoryName(dllPath); |
| 243 | + Assert.IsNotNull(dllDirectory, "DLL directory should not be null"); |
| 244 | + var target = Program.GetTarget(); |
| 245 | + |
| 246 | + // Create config file without program property |
| 247 | + var json = JsonSerializer.Serialize(new Dictionary<string, object> |
| 248 | + { |
| 249 | + { target, new { notprogram = "fake" } } |
| 250 | + }); |
| 251 | + File.WriteAllText(Path.Combine(dllDirectory, "DotnetToolWrapper.json"), json); |
| 252 | + |
| 253 | + // Act |
| 254 | + var exitCode = Runner.Run(out var output, "dotnet", dllPath); |
| 255 | + |
| 256 | + // Assert |
| 257 | + Assert.AreEqual(1, exitCode, "Exit code should be 1 for bad configuration"); |
| 258 | + Assert.IsTrue(output.Contains("Bad configuration"), "Output should mention bad configuration"); |
| 259 | + } |
| 260 | +} |
0 commit comments