diff --git a/bld.Tests/DockerfileParserTests.cs b/bld.Tests/DockerfileParserTests.cs new file mode 100644 index 0000000..55ccfea --- /dev/null +++ b/bld.Tests/DockerfileParserTests.cs @@ -0,0 +1,256 @@ +using bld.Infrastructure; + +namespace bld.Tests; + +public class DockerfileParserTests : IAsyncLifetime { + private readonly string _tempDir; + + public DockerfileParserTests() { + _tempDir = Path.Combine(Path.GetTempPath(), "bld_test_" + Guid.NewGuid().ToString("N")); + } + + public Task InitializeAsync() { + Directory.CreateDirectory(_tempDir); + return Task.CompletedTask; + } + + public Task DisposeAsync() { + if (Directory.Exists(_tempDir)) { + Directory.Delete(_tempDir, true); + } + return Task.CompletedTask; + } + + [Fact] + public async Task ParseAsync_WithSimpleDockerfile_ParsesBaseImage() { + // Arrange + var dockerfilePath = Path.Combine(_tempDir, "Dockerfile"); + await File.WriteAllTextAsync(dockerfilePath, "FROM mcr.microsoft.com/dotnet/aspnet:8.0\n"); + + // Act + var info = await DockerfileParser.ParseAsync(dockerfilePath); + + // Assert + Assert.Single(info.BaseImages); + Assert.Equal("mcr.microsoft.com/dotnet/aspnet:8.0", info.BaseImages[0]); + } + + [Fact] + public async Task ParseAsync_WithMultiStageDockerfile_ParsesAllStages() { + // Arrange + var dockerfile = @" +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src +COPY . . +RUN dotnet publish -c Release + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime +WORKDIR /app +COPY --from=build /src/bin/Release/net8.0/publish . +ENTRYPOINT [""dotnet"", ""myapp.dll""] +"; + var dockerfilePath = Path.Combine(_tempDir, "Dockerfile"); + await File.WriteAllTextAsync(dockerfilePath, dockerfile); + + // Act + var info = await DockerfileParser.ParseAsync(dockerfilePath); + + // Assert + Assert.Equal(2, info.BaseImages.Count); + Assert.Contains("mcr.microsoft.com/dotnet/sdk:8.0", info.BaseImages); + Assert.Contains("mcr.microsoft.com/dotnet/aspnet:8.0", info.BaseImages); + Assert.Equal(2, info.Stages.Count); + Assert.Contains("build", info.Stages); + Assert.Contains("runtime", info.Stages); + } + + [Fact] + public async Task ParseAsync_WithExposedPorts_ParsesPorts() { + // Arrange + var dockerfile = @" +FROM nginx:latest +EXPOSE 80 +EXPOSE 443 +"; + var dockerfilePath = Path.Combine(_tempDir, "Dockerfile"); + await File.WriteAllTextAsync(dockerfilePath, dockerfile); + + // Act + var info = await DockerfileParser.ParseAsync(dockerfilePath); + + // Assert + Assert.Equal(2, info.ExposedPorts.Count); + Assert.Contains("80", info.ExposedPorts); + Assert.Contains("443", info.ExposedPorts); + } + + [Fact] + public async Task ParseAsync_WithMultiplePortsOnOneLine_ParsesAllPorts() { + // Arrange + var dockerfile = @" +FROM nginx:latest +EXPOSE 80 443 8080 +"; + var dockerfilePath = Path.Combine(_tempDir, "Dockerfile"); + await File.WriteAllTextAsync(dockerfilePath, dockerfile); + + // Act + var info = await DockerfileParser.ParseAsync(dockerfilePath); + + // Assert + Assert.Equal(3, info.ExposedPorts.Count); + Assert.Contains("80", info.ExposedPorts); + Assert.Contains("443", info.ExposedPorts); + Assert.Contains("8080", info.ExposedPorts); + } + + [Fact] + public async Task ParseAsync_WithWorkdir_ParsesWorkdir() { + // Arrange + var dockerfile = @" +FROM node:18 +WORKDIR /app +"; + var dockerfilePath = Path.Combine(_tempDir, "Dockerfile"); + await File.WriteAllTextAsync(dockerfilePath, dockerfile); + + // Act + var info = await DockerfileParser.ParseAsync(dockerfilePath); + + // Assert + Assert.Equal("/app", info.WorkDir); + } + + [Fact] + public async Task ParseAsync_WithEntrypoint_ParsesEntrypoint() { + // Arrange + var dockerfile = @" +FROM mcr.microsoft.com/dotnet/aspnet:8.0 +ENTRYPOINT [""dotnet"", ""myapp.dll""] +"; + var dockerfilePath = Path.Combine(_tempDir, "Dockerfile"); + await File.WriteAllTextAsync(dockerfilePath, dockerfile); + + // Act + var info = await DockerfileParser.ParseAsync(dockerfilePath); + + // Assert + Assert.Equal("[\"dotnet\", \"myapp.dll\"]", info.EntryPoint); + } + + [Fact] + public async Task ParseAsync_WithCmd_ParsesCmd() { + // Arrange + var dockerfile = @" +FROM nginx:latest +CMD [""nginx"", ""-g"", ""daemon off;""] +"; + var dockerfilePath = Path.Combine(_tempDir, "Dockerfile"); + await File.WriteAllTextAsync(dockerfilePath, dockerfile); + + // Act + var info = await DockerfileParser.ParseAsync(dockerfilePath); + + // Assert + Assert.Equal("[\"nginx\", \"-g\", \"daemon off;\"]", info.Cmd); + } + + [Fact] + public async Task ParseAsync_WithComments_IgnoresComments() { + // Arrange + var dockerfile = @" +# This is a comment +FROM nginx:latest +# Another comment +EXPOSE 80 +"; + var dockerfilePath = Path.Combine(_tempDir, "Dockerfile"); + await File.WriteAllTextAsync(dockerfilePath, dockerfile); + + // Act + var info = await DockerfileParser.ParseAsync(dockerfilePath); + + // Assert + Assert.Single(info.BaseImages); + Assert.Single(info.ExposedPorts); + } + + [Fact] + public async Task ParseAsync_WithNonExistentFile_ReturnsEmptyInfo() { + // Arrange + var dockerfilePath = Path.Combine(_tempDir, "NonExistent", "Dockerfile"); + + // Act + var info = await DockerfileParser.ParseAsync(dockerfilePath); + + // Assert + Assert.Empty(info.BaseImages); + Assert.Empty(info.Stages); + Assert.Empty(info.ExposedPorts); + } + + [Fact] + public async Task FindDockerfilesAsync_WithNestedDockerfiles_FindsAll() { + // Arrange + var subDir = Path.Combine(_tempDir, "subproject"); + Directory.CreateDirectory(subDir); + + await File.WriteAllTextAsync(Path.Combine(_tempDir, "Dockerfile"), "FROM alpine"); + await File.WriteAllTextAsync(Path.Combine(subDir, "Dockerfile"), "FROM nginx"); + + // Act + var dockerfiles = await DockerfileParser.FindDockerfilesAsync(_tempDir, 3); + + // Assert + Assert.Equal(2, dockerfiles.Count); + } + + [Fact] + public async Task FindDockerfilesAsync_WithDepthLimit_RespectsLimit() { + // Arrange + var level1 = Path.Combine(_tempDir, "level1"); + var level2 = Path.Combine(level1, "level2"); + var level3 = Path.Combine(level2, "level3"); + Directory.CreateDirectory(level3); + + await File.WriteAllTextAsync(Path.Combine(_tempDir, "Dockerfile"), "FROM alpine"); + await File.WriteAllTextAsync(Path.Combine(level3, "Dockerfile"), "FROM nginx"); + + // Act + var dockerfiles = await DockerfileParser.FindDockerfilesAsync(_tempDir, 1); + + // Assert + Assert.Single(dockerfiles); // Should only find the root Dockerfile + } + + [Fact] + public async Task FindDockerfilesAsync_SkipsBinAndObjDirectories() { + // Arrange + var binDir = Path.Combine(_tempDir, "bin"); + var objDir = Path.Combine(_tempDir, "obj"); + Directory.CreateDirectory(binDir); + Directory.CreateDirectory(objDir); + + await File.WriteAllTextAsync(Path.Combine(_tempDir, "Dockerfile"), "FROM alpine"); + await File.WriteAllTextAsync(Path.Combine(binDir, "Dockerfile"), "FROM nginx"); + await File.WriteAllTextAsync(Path.Combine(objDir, "Dockerfile"), "FROM node"); + + // Act + var dockerfiles = await DockerfileParser.FindDockerfilesAsync(_tempDir, 3); + + // Assert + Assert.Single(dockerfiles); // Should only find the root Dockerfile + } + + [Fact] + public async Task FindDockerfilesAsync_WithNonExistentDirectory_ReturnsEmpty() { + // Arrange + var nonExistentPath = Path.Combine(_tempDir, "nonexistent"); + + // Act + var dockerfiles = await DockerfileParser.FindDockerfilesAsync(nonExistentPath, 3); + + // Assert + Assert.Empty(dockerfiles); + } +} diff --git a/bld.Tests/InfrastructureTests.cs b/bld.Tests/InfrastructureTests.cs new file mode 100644 index 0000000..cebba4c --- /dev/null +++ b/bld.Tests/InfrastructureTests.cs @@ -0,0 +1,185 @@ +using bld.Infrastructure; + +namespace bld.Tests; + +public class DirExtTests { + [Theory] + [InlineData("/home/user/project/src", "/home/user/project", true)] + [InlineData("/home/user/project/deep/nested/path", "/home/user/project", true)] + [InlineData("/home/user/project", "/home/user/project", false)] // Same path + [InlineData("/home/user/other", "/home/user/project", false)] + [InlineData(".", "/home/user/project", false)] + [InlineData("..", "/home/user/project", false)] + [InlineData("", "/home/user/project", false)] + public void IsNestedBelow_ReturnsExpectedResult(string target, string baseDir, bool expected) { + // Act + var result = DirExt.IsNestedBelow(target, baseDir); + + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("relative/path", "/base", true)] + [InlineData("/absolute/path", "/base", true)] + [InlineData(".", "/base", true)] + [InlineData("..", "/base", true)] + [InlineData(null, "/base", false)] + [InlineData("", "/base", false)] + public void NormalizePath_ValidatesCorrectly(string? candidate, string baseDir, bool shouldBeValid) { + // Act + var result = DirExt.NormalizePath(candidate, baseDir, out var normalized); + + // Assert + Assert.Equal(shouldBeValid, result); + if (shouldBeValid) { + Assert.NotNull(normalized); + } + } + + [Fact] + public void EnsureRooted_RelativePathBecomesRooted() { + // Arrange + var relative = "relative/path"; + var baseDir = "/base"; + + // Act + var result = DirExt.EnsureRooted(relative, baseDir); + + // Assert + Assert.True(Path.IsPathFullyQualified(result)); + Assert.Contains("relative", result); + Assert.Contains("path", result); + } + + [Fact] + public void EnsureRooted_AbsolutePathRemainsAbsolute() { + // Arrange + var absolute = "/absolute/path"; + var baseDir = "/base"; + + // Act + var result = DirExt.EnsureRooted(absolute, baseDir); + + // Assert + Assert.True(Path.IsPathFullyQualified(result)); + } + + [Fact] + public void EnsureRooted_ThrowsForDevicePaths() { + // This test only applies on Windows + if (!OperatingSystem.IsWindows()) { + return; // Skip on non-Windows platforms + } + + // Act & Assert + Assert.Throws(() => DirExt.EnsureRooted(@"\\.\COM1", @"C:\base")); + } + + [Fact] + public void Exists_ChecksDirectoryExistence() { + // Arrange + var tempDir = Path.Combine(Path.GetTempPath(), "bld_test_" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(tempDir); + + try { + // Act & Assert + Assert.True(DirExt.Exists(tempDir)); + Assert.True(DirExt.Exists(tempDir + Path.DirectorySeparatorChar)); + Assert.False(DirExt.Exists(Path.Combine(tempDir, "nonexistent"))); + } + finally { + Directory.Delete(tempDir, true); + } + } +} + +public class DirectoryInfoExtensionsTests : IAsyncLifetime { + private string _tempDir = null!; + + public Task InitializeAsync() { + _tempDir = Path.Combine(Path.GetTempPath(), "bld_test_" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(_tempDir); + return Task.CompletedTask; + } + + public Task DisposeAsync() { + if (Directory.Exists(_tempDir)) { + Directory.Delete(_tempDir, true); + } + return Task.CompletedTask; + } + + [Fact] + public void IsEmpty_ReturnsTrueForEmptyDirectory() { + // Arrange + var dirInfo = new DirectoryInfo(_tempDir); + + // Act & Assert + Assert.True(dirInfo.IsEmpty()); + Assert.False(dirInfo.IsNotEmpty()); + } + + [Fact] + public void IsEmpty_ReturnsFalseWhenContainsFiles() { + // Arrange + File.WriteAllText(Path.Combine(_tempDir, "test.txt"), "test"); + var dirInfo = new DirectoryInfo(_tempDir); + + // Act & Assert + Assert.False(dirInfo.IsEmpty()); + Assert.True(dirInfo.IsNotEmpty()); + } + + [Fact] + public void IsEmpty_ReturnsFalseWhenContainsSubdirectories() { + // Arrange + Directory.CreateDirectory(Path.Combine(_tempDir, "subdir")); + var dirInfo = new DirectoryInfo(_tempDir); + + // Act & Assert + Assert.False(dirInfo.IsEmpty()); + Assert.True(dirInfo.IsNotEmpty()); + } + + [Fact] + public void HasSubDir_FindsSubdirectory() { + // Arrange + Directory.CreateDirectory(Path.Combine(_tempDir, "obj")); + var dirInfo = new DirectoryInfo(_tempDir); + + // Act & Assert + Assert.True(dirInfo.HasSubDir("obj")); + } + + [Fact] + public void HasSubDir_ReturnsFalseWhenNotFound() { + // Arrange + var dirInfo = new DirectoryInfo(_tempDir); + + // Act & Assert + Assert.False(dirInfo.HasSubDir("obj")); + } + + [Fact] + public void OnlyHasSubDirsOrSubset_ReturnsTrueForExactMatch() { + // Arrange + Directory.CreateDirectory(Path.Combine(_tempDir, "bin")); + Directory.CreateDirectory(Path.Combine(_tempDir, "obj")); + var dirInfo = new DirectoryInfo(_tempDir); + + // Act & Assert + Assert.True(dirInfo.OnlyHasSubDirsOrSubset(true, "bin", "obj")); + } + + [Fact] + public void OnlyHasSubDirsOrSubset_ReturnsFalseWhenHasFiles() { + // Arrange + Directory.CreateDirectory(Path.Combine(_tempDir, "bin")); + File.WriteAllText(Path.Combine(_tempDir, "test.txt"), "test"); + var dirInfo = new DirectoryInfo(_tempDir); + + // Act & Assert + Assert.False(dirInfo.OnlyHasSubDirsOrSubset(true, "bin")); + } +} diff --git a/bld.Tests/ModelTests.cs b/bld.Tests/ModelTests.cs new file mode 100644 index 0000000..723e952 --- /dev/null +++ b/bld.Tests/ModelTests.cs @@ -0,0 +1,278 @@ +using bld.Models; + +namespace bld.Tests; + +public class ProjectPropertiesTests { + [Fact] + public void Indexer_ReturnsValueWhenKeyExists() { + // Arrange + var properties = new ProjectProperties { + Properties = new Dictionary { + { "OutDir", "/bin/Debug/" }, + { "TargetFramework", "net8.0" } + } + }; + + // Act & Assert + Assert.Equal("/bin/Debug/", properties["OutDir"]); + Assert.Equal("net8.0", properties["TargetFramework"]); + } + + [Fact] + public void Indexer_ReturnsNullWhenKeyDoesNotExist() { + // Arrange + var properties = new ProjectProperties { + Properties = new Dictionary() + }; + + // Act & Assert + Assert.Null(properties["NonExistentKey"]); + } + + [Fact] + public void PropertyAccessors_ReturnCorrectValues() { + // Arrange + var properties = new ProjectProperties { + Properties = new Dictionary { + { "OutDir", "/bin/Debug/net8.0/" }, + { "BaseOutputPath", "/bin/" }, + { "BaseIntermediateOutputPath", "/obj/" }, + { "PackageOutputPath", "/packages/" }, + { "AssemblyName", "MyAssembly" }, + { "PackageId", "MyPackage" }, + { "ProjectName", "MyProject" }, + { "TargetFramework", "net8.0" }, + { "TargetFrameworks", "net6.0;net7.0;net8.0" } + } + }; + + // Act & Assert + Assert.Equal("/bin/Debug/net8.0/", properties.OutDir); + Assert.Equal("/bin/", properties.BaseOutputPath); + Assert.Equal("/obj/", properties.BaseIntermediateOutputPath); + Assert.Equal("/packages/", properties.PackageOutputPath); + Assert.Equal("MyAssembly", properties.AssemblyName); + Assert.Equal("MyPackage", properties.PackageId); + Assert.Equal("MyProject", properties.ProjectName); + Assert.Equal("net8.0", properties.TargetFramework); + Assert.Equal("net6.0;net7.0;net8.0", properties.TargetFrameworks); + } + + [Fact] + public void Empty_ReturnsEmptyProperties() { + // Act + var empty = ProjectProperties.Empty; + + // Assert + Assert.NotNull(empty); + Assert.Empty(empty.Properties); + Assert.Null(empty.OutDir); + Assert.Null(empty.TargetFramework); + } +} + +public class ProjCfgTests { + [Fact] + public void Path_ReturnsProjectPath() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj = new Proj("/path/to/project.csproj", sln); + var projCfg = new ProjCfg(proj, "Debug"); + + // Act & Assert + Assert.Equal("/path/to/project.csproj", projCfg.Path); + } + + [Fact] + public void ProjDir_ReturnsProjectDirectory() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj = new Proj("/path/to/project.csproj", sln); + var projCfg = new ProjCfg(proj, "Debug"); + + // Act & Assert + Assert.Equal("/path/to", projCfg.ProjDir); + } + + [Fact] + public void ConfigurationOrDefault_ReturnsConfigurationWhenSet() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj = new Proj("/path/to/project.csproj", sln); + var projCfg = new ProjCfg(proj, "Debug"); + + // Act & Assert + Assert.Equal("Debug", projCfg.ConfigurationOrDefault); + } + + [Fact] + public void ConfigurationOrDefault_ReturnsReleaseWhenNull() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj = new Proj("/path/to/project.csproj", sln); + var projCfg = new ProjCfg(proj, null); + + // Act & Assert + Assert.Equal("Release", projCfg.ConfigurationOrDefault); + } +} + +public class ProjCfgEqualityComparerTests { + private readonly ProjCfgEqualityComparer _comparer = new(); + + [Fact] + public void Equals_ReturnsTrueForSamePathAndConfiguration() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj1 = new Proj("/path/to/project.csproj", sln); + var proj2 = new Proj("/path/to/project.csproj", sln); + var cfg1 = new ProjCfg(proj1, "Debug"); + var cfg2 = new ProjCfg(proj2, "Debug"); + + // Act & Assert + Assert.True(_comparer.Equals(cfg1, cfg2)); + } + + [Fact] + public void Equals_ReturnsTrueForCaseInsensitiveConfiguration() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj = new Proj("/path/to/project.csproj", sln); + var cfg1 = new ProjCfg(proj, "Debug"); + var cfg2 = new ProjCfg(proj, "DEBUG"); + + // Act & Assert + Assert.True(_comparer.Equals(cfg1, cfg2)); + } + + [Fact] + public void Equals_ReturnsFalseForDifferentPaths() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj1 = new Proj("/path/to/project1.csproj", sln); + var proj2 = new Proj("/path/to/project2.csproj", sln); + var cfg1 = new ProjCfg(proj1, "Debug"); + var cfg2 = new ProjCfg(proj2, "Debug"); + + // Act & Assert + Assert.False(_comparer.Equals(cfg1, cfg2)); + } + + [Fact] + public void Equals_ReturnsFalseForDifferentConfigurations() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj = new Proj("/path/to/project.csproj", sln); + var cfg1 = new ProjCfg(proj, "Debug"); + var cfg2 = new ProjCfg(proj, "Release"); + + // Act & Assert + Assert.False(_comparer.Equals(cfg1, cfg2)); + } + + [Fact] + public void Equals_ReturnsFalseForNullInputs() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj = new Proj("/path/to/project.csproj", sln); + var cfg = new ProjCfg(proj, "Debug"); + + // Act & Assert + Assert.False(_comparer.Equals(cfg, null)); + Assert.False(_comparer.Equals(null, cfg)); + Assert.False(_comparer.Equals(null, null)); + } + + [Fact] + public void Equals_ReturnsTrueForSameReference() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj = new Proj("/path/to/project.csproj", sln); + var cfg = new ProjCfg(proj, "Debug"); + + // Act & Assert + Assert.True(_comparer.Equals(cfg, cfg)); + } + + [Fact] + public void GetHashCode_ReturnsSameHashForEqualObjects() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj1 = new Proj("/path/to/project.csproj", sln); + var proj2 = new Proj("/path/to/project.csproj", sln); + var cfg1 = new ProjCfg(proj1, "Debug"); + var cfg2 = new ProjCfg(proj2, "DEBUG"); // Different case + + // Act + var hash1 = _comparer.GetHashCode(cfg1); + var hash2 = _comparer.GetHashCode(cfg2); + + // Assert + Assert.Equal(hash1, hash2); + } +} + +public class ProjTests { + [Fact] + public void Dir_ReturnsParentDirectory() { + // Arrange + var sln = new Sln("/path/to/solution.sln"); + var proj = new Proj("/path/to/project.csproj", sln); + + // Act & Assert + Assert.Equal("/path/to", proj.Dir); + } + + [Fact] + public void Dir_ThrowsForRootPath() { + // Arrange + var sln = new Sln("/solution.sln"); + + // Act & Assert - On Unix, root path "/" would be the parent, but that should work + var proj = new Proj("/project.csproj", sln); + Assert.Equal("/", proj.Dir); + } +} + +public class ProjectInfoTests { + [Fact] + public void DefaultValues_AreCorrect() { + // Arrange & Act + var info = new ProjectInfo(); + + // Assert + Assert.Equal(string.Empty, info.ProjectPath); + Assert.Null(info.ProjectName); + Assert.Null(info.AssemblyName); + Assert.Null(info.TargetFramework); + Assert.Empty(info.TargetFrameworks); + Assert.Null(info.Configuration); + Assert.Null(info.Platform); + Assert.Null(info.IntermediateOutputPath); + Assert.Null(info.PackageOutputPath); + Assert.Null(info.PackageId); + Assert.Empty(info.Properties); + Assert.False(info.HasDockerProperties); + Assert.Null(info.OutDir); + Assert.Null(info.BaseOutputPath); + } + + [Fact] + public void WithInitializer_SetsValues() { + // Arrange & Act + var info = new ProjectInfo { + ProjectPath = "/path/to/project.csproj", + ProjectName = "MyProject", + TargetFramework = "net8.0", + TargetFrameworks = new[] { "net7.0", "net8.0" }, + Configuration = "Debug" + }; + + // Assert + Assert.Equal("/path/to/project.csproj", info.ProjectPath); + Assert.Equal("MyProject", info.ProjectName); + Assert.Equal("net8.0", info.TargetFramework); + Assert.Equal(2, info.TargetFrameworks.Count); + Assert.Equal("Debug", info.Configuration); + } +} diff --git a/bld.Tests/NuGetFrameworkTests.cs b/bld.Tests/NuGetFrameworkTests.cs index 0a958b0..716357e 100644 --- a/bld.Tests/NuGetFrameworkTests.cs +++ b/bld.Tests/NuGetFrameworkTests.cs @@ -1,20 +1,18 @@ -//using XUnit.Framework; - -using NuGet.Frameworks; +using NuGet.Frameworks; using Xunit.Abstractions; using System.Reflection; namespace bld.Tests; -public class DotNetTests(ITestOutputHelper Console) { +public class DotNetTests { [Fact] public void PathCombineLinux() { - Assert.Throws(() => Path.Combine("/mnt/d/tests", null, "child")); + Assert.Throws(() => Path.Combine("/mnt/d/tests", null!, "child")); } [Fact] public void PathCombineWin() { - Assert.Throws(() => Path.Combine("d:\\tests", null, "child")); + Assert.Throws(() => Path.Combine("d:\\tests", null!, "child")); } } @@ -37,7 +35,6 @@ public class TfmCommandTests { [InlineData("NET8.0", true)] // Test case insensitivity [InlineData(" net7.0 ", true)] // Test trimming [InlineData("", false)] - [InlineData(null, false)] public void IsDotNetCoreFramework_ShouldFilterCorrectly(string tfm, bool expected) { // Use reflection to call the private static method var tfmCommandType = typeof(bld.Commands.TfmCommand); @@ -49,9 +46,24 @@ public void IsDotNetCoreFramework_ShouldFilterCorrectly(string tfm, bool expecte var result = (bool)method.Invoke(null, new object[] { tfm })!; Assert.Equal(expected, result); } + + [Fact] + public void IsDotNetCoreFramework_WithNull_ReturnsFalse() { + // Test null separately to avoid xUnit1012 warning + var tfmCommandType = typeof(bld.Commands.TfmCommand); + var method = tfmCommandType.GetMethod("IsDotNetCoreFramework", + BindingFlags.NonPublic | BindingFlags.Static); + + Assert.NotNull(method); + + var result = (bool)method.Invoke(null, new object?[] { null })!; + Assert.False(result); + } } -public class NuGetFrameworkTests(ITestOutputHelper Console) { +public class NuGetFrameworkTests(ITestOutputHelper console) { + private readonly ITestOutputHelper _console = console; + [Fact] public void Test1() { string[] tfms = new[] @@ -74,7 +86,7 @@ public void Test1() { foreach (var tfm in tfms) { var framework = NuGetFramework.Parse(tfm); string normalizedTfm = framework.GetShortFolderName(); - Console.WriteLine($"Original: {tfm}, Normalized: {normalizedTfm}"); + _console.WriteLine($"Original: {tfm}, Normalized: {normalizedTfm}"); } } } diff --git a/bld.Tests/ServiceTests.cs b/bld.Tests/ServiceTests.cs new file mode 100644 index 0000000..468066f --- /dev/null +++ b/bld.Tests/ServiceTests.cs @@ -0,0 +1,156 @@ +using bld.Services; + +namespace bld.Tests; + +public class NetUtilTests { + [Theory] + [InlineData("net8.0", true)] + [InlineData("net9.0", true)] + [InlineData("net10.0", true)] + [InlineData("NET8.0", true)] // Case insensitive + [InlineData("net7.0", false)] + [InlineData("net6.0", false)] + [InlineData("netcoreapp3.1", false)] + [InlineData("netstandard2.0", false)] + [InlineData("net48", false)] + [InlineData(null, false)] + [InlineData("", false)] + public void IsNet8OrHigher_ReturnsExpectedResult(string? tfm, bool expected) { + // Act + var result = NetUtil.IsNet8OrHigher(tfm); + + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("net8.0", true)] + [InlineData("net9.0", true)] + [InlineData("net10.0", true)] + [InlineData("netstandard2.0", true)] + [InlineData("netcoreapp3.1", true)] + [InlineData("net48", true)] + [InlineData("net472", true)] + [InlineData("invalid", false)] + [InlineData("net99.0", false)] + public void IsTfmName_ReturnsExpectedResult(string name, bool expected) { + // Act + var result = NetUtil.Instance.IsTfmName(name, StringComparison.OrdinalIgnoreCase); + + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("NET8.0", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("NET8.0", StringComparison.Ordinal, false)] + [InlineData("net8.0", StringComparison.Ordinal, true)] + public void IsTfmName_RespectsComparison(string name, StringComparison comparison, bool expected) { + // Act + var result = NetUtil.Instance.IsTfmName(name, comparison); + + // Assert + Assert.Equal(expected, result); + } +} + +public class TargetFrameworkValidatorTests { + private readonly TargetFrameworkValidator _validator = new(); + + [Theory] + [InlineData("net8.0", true)] + [InlineData("net9.0", true)] + [InlineData("netstandard2.0", true)] + [InlineData("netcoreapp3.1", true)] + [InlineData("net48", true)] + [InlineData(null, false)] + [InlineData("", false)] + [InlineData("invalid", false)] + [InlineData("net99.0", false)] + public void IsValidTfm_ReturnsExpectedResult(string? tfm, bool expected) { + // Act + var result = _validator.IsValidTfm(tfm); + + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("net8.0", true)] + [InlineData("net9.0", true)] + [InlineData("net10.0", true)] + [InlineData("net8.0-windows", true)] // Starts with net8 + [InlineData("net7.0", false)] + [InlineData("net6.0", false)] + [InlineData(null, false)] + [InlineData("", false)] + public void IsNet8OrHigher_ReturnsExpectedResult(string? tfm, bool expected) { + // Act + var result = _validator.IsNet8OrHigher(tfm); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void GetCurrentTargetFramework_ReturnsNet8() { + // Act + var result = _validator.GetCurrentTargetFramework(); + + // Assert + Assert.Equal("net8.0", result); + } + + [Theory] + [InlineData("net8.0", true)] + [InlineData("net7.0", false)] + [InlineData("net9.0", false)] + [InlineData(null, false)] + [InlineData("", false)] + public void IsCurrentTfm_ReturnsExpectedResult(string? tfm, bool expected) { + // Act + var result = _validator.IsCurrentTfm(tfm); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void FilterNonCurrentTfms_FiltersOutCurrentTfm() { + // Arrange + var tfms = new[] { "net6.0", "net7.0", "net8.0", "net9.0" }; + + // Act + var result = _validator.FilterNonCurrentTfms(tfms).ToList(); + + // Assert + Assert.Equal(3, result.Count); + Assert.Contains("net6.0", result); + Assert.Contains("net7.0", result); + Assert.Contains("net9.0", result); + Assert.DoesNotContain("net8.0", result); + } + + [Theory] + [MemberData(nameof(DockerPropertiesTestData))] + public void HasDockerProperties_ReturnsExpectedResult(Dictionary properties, bool expected) { + // Act + var result = _validator.HasDockerProperties(properties); + + // Assert + Assert.Equal(expected, result); + } + + public static TheoryData, bool> DockerPropertiesTestData => new() + { + { new Dictionary { { "ContainerBaseImage", "mcr.microsoft.com/dotnet/aspnet:8.0" } }, true }, + { new Dictionary { { "ContainerFamily", "alpine" } }, true }, + { new Dictionary { { "ContainerRegistry", "docker.io" } }, true }, + { new Dictionary { { "ContainerRepository", "myapp" } }, true }, + { new Dictionary { { "ContainerImageTag", "latest" } }, true }, + { new Dictionary { { "SomeOtherProperty", "value" } }, false }, + { new Dictionary(), false }, + { new Dictionary { { "ContainerBaseImage", "" } }, false }, + { new Dictionary { { "ContainerBaseImage", " " } }, false }, + }; +} diff --git a/bld.Tests/bld.Tests.csproj b/bld.Tests/bld.Tests.csproj index 6a7076d..c24879e 100644 --- a/bld.Tests/bld.Tests.csproj +++ b/bld.Tests/bld.Tests.csproj @@ -5,6 +5,7 @@ enable enable false + true @@ -15,12 +16,7 @@ - - - +