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
5 changes: 5 additions & 0 deletions src/DemaConsulting.VersionMark/Cli/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,11 @@ private int ParseArgument(string arg, string[] args, int index)

case "--report-depth":
ReportDepth = GetRequiredIntArgument(arg, args, index, "a depth value");
if (ReportDepth < 1)
{
throw new ArgumentException($"{arg} requires a positive integer value (minimum 1)", nameof(args));
}

return index + 1;

default:
Expand Down
21 changes: 12 additions & 9 deletions src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,17 +174,10 @@
/// (or <see langword="null"/> when fatal errors prevent loading) and a read-only list of
/// <see cref="LintIssue"/> objects describing all warnings and errors encountered.
/// </returns>
public static VersionMarkLoadResult Load(string filePath)

Check warning on line 177 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Refactor this method to reduce its Cognitive Complexity from 21 to the 15 allowed.

Check warning on line 177 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Refactor this method to reduce its Cognitive Complexity from 21 to the 15 allowed.

Check warning on line 177 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Refactor this method to reduce its Cognitive Complexity from 21 to the 15 allowed.

Check warning on line 177 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Refactor this method to reduce its Cognitive Complexity from 21 to the 15 allowed.

Check warning on line 177 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Refactor this method to reduce its Cognitive Complexity from 21 to the 15 allowed.

Check warning on line 177 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Refactor this method to reduce its Cognitive Complexity from 21 to the 15 allowed.
{
var issues = new List<LintIssue>();

// Check if file exists before attempting to parse
if (!File.Exists(filePath))
{
issues.Add(new LintIssue(filePath, 1, 1, LintSeverity.Error, "Configuration file not found"));
return new VersionMarkLoadResult(null, issues);
}

// Parse YAML, reporting any syntax errors with their source location
YamlStream yaml;
try
Expand All @@ -198,6 +191,16 @@
issues.Add(new LintIssue(filePath, ex.Start.Line + 1, ex.Start.Column + 1, LintSeverity.Error, $"Failed to parse YAML file: {ex.Message}"));
return new VersionMarkLoadResult(null, issues);
}
catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException)
{
issues.Add(new LintIssue(filePath, 1, 1, LintSeverity.Error, "Configuration file not found"));
return new VersionMarkLoadResult(null, issues);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
issues.Add(new LintIssue(filePath, 1, 1, LintSeverity.Error, $"Failed to read file: {ex.Message}"));
return new VersionMarkLoadResult(null, issues);
}

// Validate that the file contains at least one YAML document
if (yaml.Documents.Count == 0)
Expand Down Expand Up @@ -316,7 +319,7 @@
/// <param name="toolConfig">
/// Set to a <see cref="ToolConfig"/> when validation succeeds; otherwise <see langword="null"/>.
/// </param>
private static void ValidateTool(

Check warning on line 322 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Refactor this method to reduce its Cognitive Complexity from 53 to the 15 allowed.

Check warning on line 322 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Refactor this method to reduce its Cognitive Complexity from 53 to the 15 allowed.

Check warning on line 322 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Refactor this method to reduce its Cognitive Complexity from 53 to the 15 allowed.

Check warning on line 322 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Refactor this method to reduce its Cognitive Complexity from 53 to the 15 allowed.

Check warning on line 322 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Refactor this method to reduce its Cognitive Complexity from 53 to the 15 allowed.

Check warning on line 322 in src/DemaConsulting.VersionMark/Configuration/VersionMarkConfig.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Refactor this method to reduce its Cognitive Complexity from 53 to the 15 allowed.
string filePath,
string toolName,
YamlMappingNode toolNode,
Expand Down Expand Up @@ -469,7 +472,7 @@
{
try
{
return new Regex(value, RegexOptions.None, RegexTimeout);
return new Regex(value, RegexOptions.Multiline | RegexOptions.IgnoreCase, RegexTimeout);
}
catch (ArgumentException ex)
{
Expand Down Expand Up @@ -597,7 +600,7 @@
var regex = new Regex(
regexPattern,
RegexOptions.Multiline | RegexOptions.IgnoreCase,
TimeSpan.FromSeconds(1));
RegexTimeout);
var match = regex.Match(output);

if (!match.Success)
Expand Down
26 changes: 26 additions & 0 deletions test/DemaConsulting.VersionMark.Tests/Cli/ContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,32 @@ public void Context_Create_NoReportDepth_DefaultsToTwo()
Assert.AreEqual(2, context.ReportDepth);
}

/// <summary>
/// Test that --report-depth 0 throws ArgumentException.
/// What is tested: Validation rejects a depth value less than 1
/// What the assertions prove: ArgumentException is thrown for report-depth values less than 1
/// </summary>
[TestMethod]
public void Context_Create_ReportDepthZero_ThrowsArgumentException()
{
// Arrange & Act & Assert - Zero is not a valid heading depth
Assert.ThrowsExactly<ArgumentException>(() =>
Context.Create(["--publish", "--report", "output.md", "--report-depth", "0"]));
}

/// <summary>
/// Test that a negative --report-depth throws ArgumentException.
/// What is tested: Validation rejects negative depth values
/// What the assertions prove: ArgumentException is thrown for negative report-depth values
/// </summary>
[TestMethod]
public void Context_Create_ReportDepthNegative_ThrowsArgumentException()
{
// Arrange & Act & Assert - Negative values are not valid heading depths
Assert.ThrowsExactly<ArgumentException>(() =>
Context.Create(["--publish", "--report", "output.md", "--report-depth", "-1"]));
}

/// <summary>
/// Test creating a context with glob patterns after -- separator.
/// What is tested: Glob patterns after -- are captured in GlobPatterns array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

using System.Runtime.Versioning;
using DemaConsulting.VersionMark.Configuration;

namespace DemaConsulting.VersionMark.Tests.Configuration;
Expand Down Expand Up @@ -625,4 +626,41 @@ public void VersionMarkConfig_Load_IssuesContainFilePath()
File.Delete(tempFile);
}
}

/// <summary>
/// Test that an unreadable file (permission denied) returns null config with an error issue.
/// </summary>
[TestMethod]
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("osx")]
public void VersionMarkConfig_Load_UnreadableFile_ReturnsError()
{
// Skip on non-Unix platforms where Unix file permissions are not supported
if (!OperatingSystem.IsLinux() && !OperatingSystem.IsMacOS())
{
Assert.Inconclusive("Unix file permissions not supported on this platform");
return;
}

// Arrange - Create a temp file and remove all read permissions
var tempFile = Path.GetTempFileName();
try
{
File.WriteAllText(tempFile, "tools:\n dotnet:\n command: dotnet --version\n");
File.SetUnixFileMode(tempFile, UnixFileMode.None);

// Act - Attempt to load config from an unreadable file
var (config, issues) = VersionMarkConfig.Load(tempFile);

// Assert - Config should be null and issues should contain an I/O error
Assert.IsNull(config);
Assert.IsTrue(issues.Any(i => i.Severity == LintSeverity.Error));
}
finally
{
// Restore permissions so the file can be deleted
File.SetUnixFileMode(tempFile, UnixFileMode.UserRead | UnixFileMode.UserWrite);
File.Delete(tempFile);
}
}
}
Loading