Initial commit: Nix WSL Wrapper project with DDD, Clean Code, and tests

This commit is contained in:
Marek Novák
2026-07-01 02:04:43 +02:00
commit 161eb7d387
22 changed files with 932 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
using System;
using Moq;
using NixWslWrapper.Cli;
using NixWslWrapper.Core.Domain;
using NixWslWrapper.Core.Interfaces;
using Xunit;
namespace NixWslWrapper.Tests
{
public class CliTests
{
[Fact]
public void CliController_ForwardArguments_ShouldReturnExecutorExitCode()
{
var mockExecutor = new Mock<ICommandExecutor>();
var expectedResult = new ExecutionResult(42);
mockExecutor
.Setup(m => m.Execute(It.IsAny<NixArguments>(), It.IsAny<WslTarget>()))
.Returns(expectedResult);
var controller = new CliController(mockExecutor.Object);
string[] cliArgs = new[] { "run", "nixpkgs#hello" };
int exitCode = controller.Run(cliArgs);
Assert.Equal(42, exitCode);
mockExecutor.Verify(m => m.Execute(
It.Is<NixArguments>(a => a.RawArguments.Length == 2 && a.RawArguments[0] == "run"),
It.IsAny<WslTarget>()
), Times.Once);
}
[Fact]
public void CliController_HelpMenuFlag_ShouldReturnZeroImmediately()
{
var mockExecutor = new Mock<ICommandExecutor>();
var controller = new CliController(mockExecutor.Object);
string[] cliArgs = new[] { "--wsl-help" };
int exitCode = controller.Run(cliArgs);
Assert.Equal(0, exitCode);
mockExecutor.Verify(m => m.Execute(It.IsAny<NixArguments>(), It.IsAny<WslTarget>()), Times.Never);
}
}
}
+71
View File
@@ -0,0 +1,71 @@
using System;
using NixWslWrapper.Core.Domain;
using Xunit;
namespace NixWslWrapper.Tests
{
public class CoreTests
{
[Fact]
public void WslTarget_ValidParameters_ShouldConstructCorrectly()
{
var target = new WslTarget("Debian", "root", "/some/path");
Assert.Equal("Debian", target.Distribution);
Assert.Equal("root", target.User);
Assert.Equal("/some/path", target.NixProfilePath);
}
[Theory]
[InlineData("", "root", "/path")]
[InlineData("Debian", "", "/path")]
[InlineData("Debian", "root", "")]
[InlineData(null, "root", "/path")]
[InlineData("Debian", null, "/path")]
[InlineData("Debian", "root", null)]
public void WslTarget_InvalidParameters_ShouldThrowArgumentException(string? distro, string? user, string? profile)
{
Assert.Throws<ArgumentException>(() => new WslTarget(distro!, user!, profile!));
}
[Fact]
public void WslTarget_Equality_ShouldBeTrueForSameValues()
{
var target1 = new WslTarget("Debian", "root", "/path");
var target2 = new WslTarget("debian", "ROOT", "/path"); // case-insensitive for distro/user
Assert.Equal(target1, target2);
}
[Fact]
public void NixArguments_EmptyArgs_ShouldReturnEmptyEscapedString()
{
var args = new NixArguments(Array.Empty<string>());
Assert.Equal(string.Empty, args.ToEscapedString());
}
[Fact]
public void NixArguments_WithSpecialCharacters_ShouldEscapeDoubleQuotes()
{
var args = new NixArguments(new[] { "run", "nixpkgs#hello", "--argstr", "msg", "hello \"world\"" });
string escaped = args.ToEscapedString();
Assert.Equal("\"run\" \"nixpkgs#hello\" \"--argstr\" \"msg\" \"hello \\\"world\\\"\"", escaped);
}
[Fact]
public void ExecutionResult_Success_ShouldSetCorrectProperties()
{
var result = ExecutionResult.Success();
Assert.True(result.IsSuccess);
Assert.Equal(0, result.ExitCode);
Assert.Null(result.ErrorMessage);
}
[Fact]
public void ExecutionResult_Failure_ShouldSetCorrectProperties()
{
var result = ExecutionResult.Failure(5, "Access Denied");
Assert.False(result.IsSuccess);
Assert.Equal(5, result.ExitCode);
Assert.Equal("Access Denied", result.ErrorMessage);
}
}
}
@@ -0,0 +1 @@
global using Xunit;
@@ -0,0 +1,52 @@
using System;
using System.IO;
using System.Text;
using NixWslWrapper.Infrastructure.IO;
using NixWslWrapper.Infrastructure.Executors;
using Xunit;
namespace NixWslWrapper.Tests
{
public class InfrastructureTests
{
[Fact]
public void ThreadedStreamCopier_ValidStream_ShouldCopyEntireContent()
{
var content = "Hello, Nix WSL Wrapper! Stream copying should work flawlessly.";
byte[] bytes = Encoding.UTF8.GetBytes(content);
using var input = new MemoryStream(bytes);
using var output = new MemoryStream();
var copier = new ThreadedStreamCopier(64); // small buffer to force multiple reads
copier.Copy(input, output);
byte[] resultBytes = output.ToArray();
string result = Encoding.UTF8.GetString(resultBytes);
Assert.Equal(content, result);
}
[Fact]
public void ProcessBuilder_ValidConfiguration_ShouldBuildCorrectProcessStartInfo()
{
var builder = new ProcessBuilder()
.SetFileName("my-app.exe")
.SetArguments("--arg1 val1")
.RedirectInput(true)
.RedirectOutput(true)
.RedirectError(true)
.UseShellExecute(false)
.CreateNoWindow(true);
var startInfo = builder.Build();
Assert.Equal("my-app.exe", startInfo.FileName);
Assert.Equal("--arg1 val1", startInfo.Arguments);
Assert.True(startInfo.RedirectStandardInput);
Assert.True(startInfo.RedirectStandardOutput);
Assert.True(startInfo.RedirectStandardError);
Assert.False(startInfo.UseShellExecute);
Assert.True(startInfo.CreateNoWindow);
}
}
}
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\NixWslWrapper.Core\NixWslWrapper.Core.csproj" />
<ProjectReference Include="..\..\src\NixWslWrapper.Infrastructure\NixWslWrapper.Infrastructure.csproj" />
<ProjectReference Include="..\..\src\NixWslWrapper.Cli\NixWslWrapper.Cli.csproj" />
</ItemGroup>
</Project>