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
3 changes: 3 additions & 0 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Check Environment Variables
run: 'echo "HOME variable is: [$HOME]" && echo "XDG_CONFIG_HOME is: [$XDG_CONFIG_HOME]"'

- name: Run Linter
run: task lint

Expand Down
68 changes: 24 additions & 44 deletions src/DotNetPathUtils.Tests/PathEnvironmentHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ public PathEnvironmentHelperTests()
public async Task EnsureDirectoryIsInPath_When_Path_Does_Not_Exist_Adds_It()
{
// Arrange
var directoryToAdd = @"C:\MyTool";
var existingPath = @"C:\ExistingPath";
var expectedNewPath = $"{existingPath}{Path.PathSeparator}{directoryToAdd}";
var rootDir = Path.GetPathRoot(Directory.GetCurrentDirectory()) ?? "/";
var directoryToAdd = Path.Combine(rootDir, "MyTool");
var existingDir = Path.Combine(rootDir, "ExistingPath");
var expectedNewPath = $"{existingDir}{Path.PathSeparator}{directoryToAdd}";

_service.GetFullPath(Arg.Any<string>()).Returns(x => (string)x[0]); // Simple pass-through
_service.GetFullPath(Arg.Any<string>()).Returns(x => (string)x[0]);
_service
.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User)
.Returns(existingPath);
_service.IsWindows().Returns(true);
.Returns(existingDir);
_service.IsWindows().Returns(OperatingSystem.IsWindows()); // Use the real OS for the test

// Act
var result = _helper.EnsureDirectoryIsInPath(
Expand All @@ -38,50 +39,38 @@ public async Task EnsureDirectoryIsInPath_When_Path_Does_Not_Exist_Adds_It()
);

// Assert
// await Assert.That(result).IsEqualTo(PathUpdateResult.PathAlreadyExists);
await Assert.That(result).IsEqualTo(PathUpdateResult.PathAdded);

_service
.Received(1)
.SetEnvironmentVariable("PATH", expectedNewPath, EnvironmentVariableTarget.User);
_service.Received(1).BroadcastEnvironmentChange();
}

[Test]
public async Task EnsureDirectoryIsInPath_When_Path_Already_Exists_Returns_AlreadyExists()
{
// Arrange
// 1. Create a root directory that is valid for the current OS.
var rootDir = Path.GetPathRoot(Directory.GetCurrentDirectory());
if (string.IsNullOrEmpty(rootDir))
{
// Fallback for non-standard filesystems (e.g. running in certain containers)
rootDir = OperatingSystem.IsWindows() ? @"C:\" : "/";
}

// 2. Build platform-agnostic paths using Path.Combine.
// 1. Build platform-agnostic paths
var rootDir = OperatingSystem.IsWindows() ? @"C:\" : "/";
var directoryToAdd = Path.Combine(rootDir, "MyTool");
var otherExistingDir = Path.Combine(rootDir, "ExistingPath");
var existingPath = $"{otherExistingDir}{Path.PathSeparator}{directoryToAdd}";

// 3. Make the mock behave more realistically for this test.
// It should just return the input, as we are providing "normalized" paths already.
// 2. Setup mocks
_service.GetFullPath(Arg.Any<string>()).Returns(x => (string)x[0]);
_service
.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User)
.GetEnvironmentVariable("PATH", Arg.Any<EnvironmentVariableTarget>())
.Returns(existingPath);

// Act
// 3. THIS IS THE FIX: Ensure we are calling the correct method.
var result = _helper.EnsureDirectoryIsInPath(
directoryToAdd,
EnvironmentVariableTarget.User
);

// Assert
await Assert.That(result).IsEqualTo(PathUpdateResult.PathAlreadyExists);

_service.DidNotReceiveWithAnyArgs().SetEnvironmentVariable(default!, default, default);
_service.DidNotReceive().BroadcastEnvironmentChange();
}

[Test]
Expand Down Expand Up @@ -173,16 +162,14 @@ public async Task EnsureDirectoryIsInPath_When_Current_Path_Is_Null_Or_Empty_Add
)
{
// Arrange
var directoryToAdd = @"C:\MyNewTool";
var rootDir = Path.GetPathRoot(Directory.GetCurrentDirectory()) ?? "/";
var directoryToAdd = Path.Combine(rootDir, "MyNewTool");

// Setup the service to return the specified input (null or empty) for the current PATH
_service
.GetEnvironmentVariable("PATH", Arg.Any<EnvironmentVariableTarget>())
.Returns(currentPath);

// Standard setup for mocks
_service.GetFullPath(directoryToAdd).Returns(directoryToAdd);
_service.IsWindows().Returns(true);
_service.IsWindows().Returns(OperatingSystem.IsWindows()); // Use the real OS

// Act
var result = _helper.EnsureDirectoryIsInPath(
Expand All @@ -191,45 +178,38 @@ public async Task EnsureDirectoryIsInPath_When_Current_Path_Is_Null_Or_Empty_Add
);

// Assert
// 1. Verify the operation reported success
await Assert.That(result).IsEqualTo(PathUpdateResult.PathAdded);

// 2. Verify SetEnvironmentVariable was called with *only the new path*,
// since the original was empty. No leading path separator should be present.
_service
.Received(1)
.SetEnvironmentVariable("PATH", directoryToAdd, EnvironmentVariableTarget.User);

// 3. Verify the environment change was broadcast (since IsWindows is true)
_service.Received(1).BroadcastEnvironmentChange();
}

[Test]
public async Task EnsureDirectoryIsInPath_When_Existing_Path_Contains_Invalid_Entry_Does_Not_Crash()
{
// Arrange
var directoryToAdd = @"C:\GoodPath";
var invalidEntry = @"C:\Bad<Path";
var existingPath = $@"C:\AnotherPath{Path.PathSeparator}{invalidEntry}";
var rootDir = Path.GetPathRoot(Directory.GetCurrentDirectory()) ?? "/";
var directoryToAdd = Path.Combine(rootDir, "GoodPath");
var otherExistingDir = Path.Combine(rootDir, "AnotherPath");
// We can still use a known invalid character for the test's purpose.
var invalidEntry = otherExistingDir + "<";
var existingPath = $"{otherExistingDir}{Path.PathSeparator}{invalidEntry}";

_service
.GetEnvironmentVariable("PATH", Arg.Any<EnvironmentVariableTarget>())
.Returns(existingPath);
_service.GetFullPath(directoryToAdd).Returns(directoryToAdd); // Normal behavior for the good path
_service.GetFullPath(@"C:\AnotherPath").Returns(@"C:\AnotherPath");
_service.GetFullPath(directoryToAdd).Returns(directoryToAdd);
_service.GetFullPath(otherExistingDir).Returns(otherExistingDir);
_service.GetFullPath(invalidEntry).Throws<ArgumentException>(); // Mock the failure

// Make GetFullPath throw *only* for the invalid entry
_service.GetFullPath(invalidEntry).Throws<ArgumentException>();
// Act
var result = _helper.EnsureDirectoryIsInPath(
directoryToAdd,
EnvironmentVariableTarget.User
);

// Assert
// The helper should have gracefully ignored the bad entry and added the new one
await Assert.That(result).IsEqualTo(PathUpdateResult.PathAdded);

var expectedNewPath = $"{existingPath}{Path.PathSeparator}{directoryToAdd}";
_service
.Received(1)
Expand Down
16 changes: 13 additions & 3 deletions src/DotNetPathUtils/PathEnvironmentHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
}

public PathUpdateResult EnsureApplicationXdgConfigDirectoryIsInPath(
EnvironmentVariableTarget target = EnvironmentVariableTarget.User
EnvironmentVariableTarget target = EnvironmentVariableTarget.User,
string? appName = null
)
{
string? appName = _service.GetApplicationName();
appName ??= _service.GetApplicationName();
if (string.IsNullOrWhiteSpace(appName))
return PathUpdateResult.Error;

Expand All @@ -30,7 +31,6 @@
return PathUpdateResult.Error;

string appConfigPath = Path.Combine(configHome, appName);
_service.CreateDirectory(appConfigPath);

return EnsureDirectoryIsInPath(appConfigPath, target);
}
Expand All @@ -42,6 +42,16 @@
{
if (string.IsNullOrWhiteSpace(directoryPath))
throw new ArgumentNullException(nameof(directoryPath));

try
{
_service.CreateDirectory(directoryPath);
}
catch (Exception ex)

Check warning on line 50 in src/DotNetPathUtils/PathEnvironmentHelper.cs

View workflow job for this annotation

GitHub Actions / Build & Test PR

The variable 'ex' is declared but never used

Check warning on line 50 in src/DotNetPathUtils/PathEnvironmentHelper.cs

View workflow job for this annotation

GitHub Actions / Build & Test PR

The variable 'ex' is declared but never used
{
return PathUpdateResult.Error;
}

if (
target == EnvironmentVariableTarget.Process
&& _pathVariableName.Equals("PATH", StringComparison.OrdinalIgnoreCase)
Expand Down