Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 72 additions & 123 deletions src/DemaConsulting.NuGet.CacheTool/Validation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,65 +101,22 @@ private static void PrintValidationHeader(Context context)
/// <param name="testResults">The test results collection.</param>
private static void RunVersionTest(Context context, DemaConsulting.TestResults.TestResults testResults)
{
var startTime = DateTime.UtcNow;
var test = CreateTestResult("NuGetCache_VersionDisplay");

try
{
using var tempDir = new TemporaryDirectory();
var logFile = PathHelpers.SafePathCombine(tempDir.DirectoryPath, "version-test.log");

// Build command line arguments
var args = new List<string>
{
"--silent",
"--log", logFile,
"--version"
};

// Run the program
int exitCode;
using (var testContext = Context.Create([.. args]))
{
Program.Run(testContext);
exitCode = testContext.ExitCode;
}

// Check if execution succeeded
if (exitCode == 0)
RunValidationTest(
context,
testResults,
"NuGetCache_VersionDisplay",
"Version Display Test",
["--version"],
logContent =>
{
// Read log content
var logContent = File.ReadAllText(logFile);

// Verify version string is in log (version contains dots like 0.0.0)
if (!string.IsNullOrWhiteSpace(logContent) &&
logContent.Split('.').Length >= 3)
{
test.Outcome = DemaConsulting.TestResults.TestOutcome.Passed;
context.WriteLine($"✓ Version Display Test - PASSED");
}
else
if (!string.IsNullOrWhiteSpace(logContent) && logContent.Split('.').Length >= 3)
{
test.Outcome = DemaConsulting.TestResults.TestOutcome.Failed;
test.ErrorMessage = "Version string not found in log";
context.WriteError($"✗ Version Display Test - FAILED: Version string not found in log");
return null;
}
}
else
{
test.Outcome = DemaConsulting.TestResults.TestOutcome.Failed;
test.ErrorMessage = $"Program exited with code {exitCode}";
context.WriteError($"✗ Version Display Test - FAILED: Exit code {exitCode}");
}
}
// Generic catch is justified here as this is a test framework - any exception should be
// recorded as a test failure to ensure robust test execution and reporting.
catch (Exception ex)
{
HandleTestException(test, context, "Version Display Test", ex);
}

FinalizeTestResult(test, startTime, testResults);
return "Version string not found in log";
});
}

/// <summary>
Expand All @@ -169,64 +126,22 @@ private static void RunVersionTest(Context context, DemaConsulting.TestResults.T
/// <param name="testResults">The test results collection.</param>
private static void RunHelpTest(Context context, DemaConsulting.TestResults.TestResults testResults)
{
var startTime = DateTime.UtcNow;
var test = CreateTestResult("NuGetCache_HelpDisplay");

try
{
using var tempDir = new TemporaryDirectory();
var logFile = PathHelpers.SafePathCombine(tempDir.DirectoryPath, "help-test.log");

// Build command line arguments
var args = new List<string>
RunValidationTest(
context,
testResults,
"NuGetCache_HelpDisplay",
"Help Display Test",
["--help"],
logContent =>
{
"--silent",
"--log", logFile,
"--help"
};

// Run the program
int exitCode;
using (var testContext = Context.Create([.. args]))
{
Program.Run(testContext);
exitCode = testContext.ExitCode;
}

// Check if execution succeeded
if (exitCode == 0)
{
// Read log content
var logContent = File.ReadAllText(logFile);

// Verify help text is in log
if (logContent.Contains("Usage:") && logContent.Contains("Options:"))
{
test.Outcome = DemaConsulting.TestResults.TestOutcome.Passed;
context.WriteLine($"✓ Help Display Test - PASSED");
}
else
{
test.Outcome = DemaConsulting.TestResults.TestOutcome.Failed;
test.ErrorMessage = "Help text not found in log";
context.WriteError($"✗ Help Display Test - FAILED: Help text not found in log");
return null;
}
}
else
{
test.Outcome = DemaConsulting.TestResults.TestOutcome.Failed;
test.ErrorMessage = $"Program exited with code {exitCode}";
context.WriteError($"✗ Help Display Test - FAILED: Exit code {exitCode}");
}
}
// Generic catch is justified here as this is a test framework - any exception should be
// recorded as a test failure to ensure robust test execution and reporting.
catch (Exception ex)
{
HandleTestException(test, context, "Help Display Test", ex);
}

FinalizeTestResult(test, startTime, testResults);
return "Help text not found in log";
});
}

/// <summary>
Expand All @@ -235,22 +150,56 @@ private static void RunHelpTest(Context context, DemaConsulting.TestResults.Test
/// <param name="context">The context for output.</param>
/// <param name="testResults">The test results collection.</param>
private static void RunCachePackageTest(Context context, DemaConsulting.TestResults.TestResults testResults)
{
RunValidationTest(
context,
testResults,
"NuGetCache_CachePackage",
"Cache Package Test",
["DemaConsulting.NuGet.Caching:0.1.0"],
logContent =>
{
// Verify that a non-empty path was written to the log
if (!string.IsNullOrWhiteSpace(logContent))
{
return null;
}

return "Package path not found in log";
});
}

/// <summary>
/// Runs a validation test with common test execution logic.
/// </summary>
/// <param name="context">The context for output.</param>
/// <param name="testResults">The test results collection.</param>
/// <param name="testName">The name of the test.</param>
/// <param name="displayName">The display name for console output.</param>
/// <param name="additionalArgs">Additional command-line arguments for the test.</param>
/// <param name="validator">
/// Function to validate test results. Receives log content and returns null on
/// success or an error message on failure.
/// </param>
private static void RunValidationTest(
Context context,
DemaConsulting.TestResults.TestResults testResults,
string testName,
string displayName,
string[] additionalArgs,
Func<string, string?> validator)
{
var startTime = DateTime.UtcNow;
var test = CreateTestResult("NuGetCache_CachePackage");
var test = CreateTestResult(testName);

try
{
using var tempDir = new TemporaryDirectory();
var logFile = PathHelpers.SafePathCombine(tempDir.DirectoryPath, "cache-package-test.log");
var logFile = PathHelpers.SafePathCombine(tempDir.DirectoryPath, $"{testName}.log");

// Build command line arguments to cache a known package
var args = new List<string>
{
"--silent",
"--log", logFile,
"DemaConsulting.NuGet.Caching:0.1.0"
};
// Build command line arguments: always use --silent and --log for consistent capture
var args = new List<string> { "--silent", "--log", logFile };
args.AddRange(additionalArgs);

// Run the program
int exitCode;
Expand All @@ -263,34 +212,34 @@ private static void RunCachePackageTest(Context context, DemaConsulting.TestResu
// Check if execution succeeded
if (exitCode == 0)
{
// Read log content to verify a path was written
// Read log content and invoke the validator
var logContent = File.ReadAllText(logFile);
var errorMessage = validator(logContent);

// Verify that a non-empty path was written to the log
if (!string.IsNullOrWhiteSpace(logContent))
if (errorMessage == null)
{
test.Outcome = DemaConsulting.TestResults.TestOutcome.Passed;
context.WriteLine($"✓ Cache Package Test - PASSED");
context.WriteLine($"✓ {displayName} - PASSED");
}
else
{
test.Outcome = DemaConsulting.TestResults.TestOutcome.Failed;
test.ErrorMessage = "Package path not found in log";
context.WriteError($"✗ Cache Package Test - FAILED: Package path not found in log");
test.ErrorMessage = errorMessage;
context.WriteError($"✗ {displayName} - FAILED: {errorMessage}");
}
}
else
{
test.Outcome = DemaConsulting.TestResults.TestOutcome.Failed;
test.ErrorMessage = $"Program exited with code {exitCode}";
context.WriteError($"✗ Cache Package Test - FAILED: Exit code {exitCode}");
context.WriteError($"✗ {displayName} - FAILED: Exit code {exitCode}");
}
}
// Generic catch is justified here as this is a test framework - any exception should be
// recorded as a test failure to ensure robust test execution and reporting.
catch (Exception ex)
{
HandleTestException(test, context, "Cache Package Test", ex);
HandleTestException(test, context, displayName, ex);
}

FinalizeTestResult(test, startTime, testResults);
Expand Down
76 changes: 76 additions & 0 deletions test/DemaConsulting.NuGet.CacheTool.Tests/ContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,80 @@ public void Context_WriteLine_Silent_DoesNotWriteToConsole()
Console.SetOut(originalOut);
}
}

/// <summary>
/// Test WriteError sets exit code to 1.
/// </summary>
[TestMethod]
public void Context_WriteError_SetsErrorExitCode()
{
// Arrange
var originalOut = Console.Out;
try
{
using var outWriter = new StringWriter();
Console.SetOut(outWriter);
using var context = Context.Create([]);

// Act
Assert.AreEqual(0, context.ExitCode);
context.WriteError("Test error");

// Assert
Assert.AreEqual(1, context.ExitCode);
}
finally
{
Console.SetOut(originalOut);
}
}

/// <summary>
/// Test WriteError writes message to console when not silent.
/// </summary>
[TestMethod]
public void Context_WriteError_NotSilent_WritesToConsole()
{
// Arrange
var originalOut = Console.Out;
try
{
using var outWriter = new StringWriter();
Console.SetOut(outWriter);
using var context = Context.Create([]);

// Act
context.WriteError("Test error message");

// Assert
var output = outWriter.ToString();
Assert.Contains("Test error message", output);
}
finally
{
Console.SetOut(originalOut);
}
}

/// <summary>
/// Test creating a context with --log flag but no value throws ArgumentException.
/// </summary>
[TestMethod]
public void Context_Create_LogFlag_WithoutValue_ThrowsArgumentException()
{
// Act & Assert
var exception = Assert.Throws<ArgumentException>(() => Context.Create(["--log"]));
Assert.Contains("--log", exception.Message);
}

/// <summary>
/// Test creating a context with --results flag but no value throws ArgumentException.
/// </summary>
[TestMethod]
public void Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException()
{
// Act & Assert
var exception = Assert.Throws<ArgumentException>(() => Context.Create(["--results"]));
Assert.Contains("--results", exception.Message);
}
}
39 changes: 39 additions & 0 deletions test/DemaConsulting.NuGet.CacheTool.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,45 @@ public void IntegrationTest_ValidateWithResults_GeneratesTrxFile()
}
}

/// <summary>
/// Test that validate with results flag generates JUnit XML file.
/// </summary>
[TestMethod]
public void IntegrationTest_ValidateWithResults_GeneratesJUnitFile()
{
// Arrange
var resultsFile = Path.GetTempFileName();
resultsFile = Path.ChangeExtension(resultsFile, ".xml");

try
{
// Act
var exitCode = Runner.Run(
out var _,
"dotnet",
_dllPath,
"--validate",
"--results",
resultsFile);

// Assert
Assert.AreEqual(0, exitCode);
Assert.IsTrue(File.Exists(resultsFile), "JUnit results file was not created");

var content = File.ReadAllText(resultsFile);
Assert.Contains("<testsuites", content);
Assert.Contains("<testsuite", content);
Assert.Contains("<testcase", content);
}
finally
{
if (File.Exists(resultsFile))
{
File.Delete(resultsFile);
}
}
}

/// <summary>
/// Test that silent flag suppresses output.
/// </summary>
Expand Down
Loading