Skip to content
Draft
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
2 changes: 1 addition & 1 deletion src/Husky/Services/Git.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ private async Task<string> GetHuskyPath()
catch (Exception e)
{
e.Message.LogVerbose(ConsoleColor.DarkRed);
throw new CommandException("Could not find Husky path", innerException: e);
throw new CommandException("Could not find Husky path. Make sure Husky is installed (run: dotnet husky install)", innerException: e);
}
}

Expand Down
21 changes: 17 additions & 4 deletions src/Husky/Services/HuskyCliWrap.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.ComponentModel;
using System.Runtime.InteropServices;
using CliWrap;
using CliWrap.Buffered;
Expand All @@ -19,9 +20,9 @@ public async Task<BufferedCommandResult> ExecBufferedAsync(string fileName, stri
.ExecuteBufferedAsync();
return result;
}
catch (Exception)
catch (Exception e)
{
$"failed to execute command '{fileName}'".LogErr();
LogCommandError(fileName, e);
throw;
}
}
Expand All @@ -36,9 +37,9 @@ public async Task<BufferedCommandResult> ExecBufferedAsync(string fileName, IEnu
.ExecuteBufferedAsync();
return result;
}
catch (Exception)
catch (Exception e)
{
$"failed to execute command '{fileName}'".LogErr();
LogCommandError(fileName, e);
throw;
}
}
Expand Down Expand Up @@ -149,4 +150,16 @@ private static void LogStandardError(string stdout, OutputTypes output)
);
}
}

private static void LogCommandError(string fileName, Exception e)
{
const int errorFileNotFound = 2;
const int errorPathNotFound = 3;

if (e is Win32Exception { NativeErrorCode: errorFileNotFound or errorPathNotFound } ||
e.InnerException is Win32Exception { NativeErrorCode: errorFileNotFound or errorPathNotFound })
$"'{fileName}' executable not found. Make sure '{fileName}' is installed and available in your PATH.".LogErr();
else
$"failed to execute command '{fileName}'".LogErr();
}
}
46 changes: 46 additions & 0 deletions tests/HuskyIntegrationTests/GitErrorMessageTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using FluentAssertions;

namespace HuskyIntegrationTests;

/// <summary>
/// Integration tests that verify the error messages shown when git is unavailable
/// or when Husky has not been installed (dotnet husky install was not run).
/// </summary>
public class GitErrorMessageTests(ITestOutputHelper output)
{
[Fact]
public async Task HuskyRun_WhenHuskyIsNotInstalled_ShouldShowInstallHintInErrorMessage()
{
// arrange: set up git and husky tool WITHOUT running dotnet husky install
await using var c = await DockerHelper.StartContainerAsync();
await c.BashAsync("dotnet new tool-manifest");
await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky --version 99.1.1-test");
await c.BashAsync("dotnet tool restore");
await c.BashAsync("git init");
// Intentionally NOT running: dotnet husky install

// act
var result = await c.BashAsync(output, "dotnet husky run");

// assert
result.ExitCode.Should().NotBe(0);
result.Stderr.Should().Contain("dotnet husky install");
}

[Fact]
public async Task HuskyRun_WhenGitIsNotAvailable_ShouldShowHelpfulErrorMessage()
{
// arrange: fully install husky, then remove the git binary from PATH to simulate it being missing
await using var c = await DockerHelper.StartWithInstalledHusky();
await c.BashAsync("mv $(which git) /tmp/git_backup");

// act
var result = await c.BashAsync(output, "dotnet husky run");

// assert
result.ExitCode.Should().NotBe(0);
var allOutput = result.Stdout + result.Stderr;
allOutput.Should().Contain("not found");
allOutput.Should().Contain("PATH");
}
}
18 changes: 18 additions & 0 deletions tests/HuskyTest/Services/GitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ await act.Should()
.WithMessage("Could not find the staged files");
}

[Fact]
public async Task GetHuskyPath_WhenGitCommandFails_ThrowsCommandExceptionWithInstallHint()
{
// Arrange
var git = new Git(_cliWrap);
var now = DateTime.UtcNow;
_cliWrap.ExecBufferedAsync("git", "config --get core.hooksPath")
.Returns(Task.FromResult(new CliWrap.Buffered.BufferedCommandResult(1, now, now, string.Empty, string.Empty)));

// Act
Func<Task> act = async () => await git.GetHuskyPathAsync();

// Assert
await act.Should()
.ThrowAsync<CommandException>()
.WithMessage("*dotnet husky install*");
}

[Fact]
public async Task GetStagedFiles_Return_StagedFiles()
{
Expand Down
Loading