// Copyright (c) The Geekeey Authors // SPDX-License-Identifier: EUPL-1.2 using System.Diagnostics; using System.Text.RegularExpressions; namespace Geekeey.Arcade.NET.Tests; public sealed class SdkPackageIntegrationTests { private static string RepositoryPath { get { for (var directory = new DirectoryInfo(AppContext.BaseDirectory); directory is not null; directory = directory.Parent) { if (File.Exists(Path.Combine(directory.FullName, "global.json"))) { return directory.FullName; } } throw new InvalidOperationException("Could not locate the repository root containing global.json."); } } private static string NuGetFeedPath { get; } = Path.Combine(RepositoryPath, "artifacts", "package", "release"); [Test] [Arguments("Geekeey.Arcade.NET.Sdk", "base")] [Arguments("Geekeey.Arcade.NET.Sdk.Razor", "razor")] [Arguments("Geekeey.Arcade.NET.Sdk.Web", "web")] public async Task SdkPackageCanBeConsumedFromAFeed(string packageId, string name) { // Arrange var version = FindPackageVersion(packageId); using var sut = await SdkTestProject.CreateAsync(name, version); // Act await sut.RunDotnetAsync($"restore --configfile \"{sut.NuGetConfigPath}\" --packages \"{sut.PackagesPath}\" --nologo"); await sut.RunDotnetAsync("build --no-restore --nologo"); await sut.RunDotnetAsync($"pack --no-restore --configuration Debug --output \"{sut.PackedPath}\" --nologo"); // Assert await Assert.That(File.Exists(Path.Combine(sut.ProjectPath, "bin", "Debug", "net10.0", $"{sut.ProjectName}.dll"))).IsTrue(); await Assert.That(Directory.Exists(sut.PackedPath)).IsTrue(); await Assert.That(Directory.EnumerateFiles(sut.PackedPath, $"{sut.ProjectName}.*.nupkg").Any()).IsTrue(); } [Test] public async Task TestSdkPackageStoresTestOutputInArtifacts() { // Arrange var version = FindPackageVersion("Geekeey.Arcade.NET.Sdk.Test"); using var sut = await SdkTestProject.CreateAsync("test", version); await sut.RunDotnetAsync($"restore --configfile \"{sut.NuGetConfigPath}\" --packages \"{sut.PackagesPath}\" --nologo"); await sut.RunDotnetAsync("build --no-restore --nologo"); var outputDirectory = Path.Combine(sut.ProjectPath, "artifacts", "bin", sut.ProjectName, "debug"); await Assert.That(File.Exists(Path.Combine(outputDirectory, $"{sut.ProjectName}.dll"))).IsTrue(); // Act var stdout = await sut.RunDotnetAsync("run --no-build --no-restore"); // Assert await Assert.That(stdout.Contains("succeeded: 1", StringComparison.Ordinal)).IsTrue(); } private static string FindPackageVersion(string packageId) { var pattern = $"""^{Regex.Escape(packageId)}\.(?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\.nupkg$"""; foreach (var package in Directory.EnumerateFiles(NuGetFeedPath, "*.nupkg")) { var match = Regex.Match(Path.GetFileName(package), pattern, RegexOptions.CultureInvariant); if (match.Success) { return match.Groups["version"].Value; } } throw new InvalidOperationException($"No current package found for {packageId}. Run dotnet pack first."); } private sealed class SdkTestProject : IDisposable { private SdkTestProject(string root) { RootPath = root; ProjectPath = Path.Combine(RootPath, "Project"); PackagesPath = Path.Combine(RootPath, "packages"); PackedPath = Path.Combine(RootPath, "packed"); NuGetConfigPath = Path.Combine(RootPath, "NuGet.config"); } public static async Task CreateAsync(string name, string version) { var sut = new SdkTestProject(Path.Combine(Path.GetTempPath(), $"arcade-sdk-test-{Guid.NewGuid():N}")); try { Directory.CreateDirectory(sut.ProjectPath); Directory.Copy(Path.Combine(AppContext.BaseDirectory, "fixtures", name), sut.ProjectPath); sut.ProjectFilePath = Directory.EnumerateFiles(sut.ProjectPath, "*.csproj").Single(); sut.ProjectName = Path.GetFileNameWithoutExtension(sut.ProjectFilePath); var project = await File.ReadAllTextAsync(sut.ProjectFilePath); await File.WriteAllTextAsync(sut.ProjectFilePath, project.Replace("{0}", version, StringComparison.Ordinal)); var contents = $""" """; await File.WriteAllTextAsync(sut.NuGetConfigPath, contents); return sut; } catch { sut.Dispose(); throw; } } public string RootPath { get; } public string ProjectPath { get; } public string ProjectFilePath { get; private set; } = ""; public string ProjectName { get; private set; } = ""; public string PackagesPath { get; } public string PackedPath { get; } public string NuGetConfigPath { get; } public async Task RunDotnetAsync(string arguments) { var startInfo = new ProcessStartInfo("dotnet", arguments) { WorkingDirectory = ProjectPath, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, Environment = { ["NUGET_PACKAGES"] = PackagesPath, ["DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER"] = "1" } }; var process = Process.Start(startInfo) ?? throw new InvalidOperationException("Could not start dotnet."); var output = await process.StandardOutput.ReadToEndAsync(); var error = await process.StandardError.ReadToEndAsync(); await process.WaitForExitAsync(); return process.ExitCode is 0 ? output : throw new InvalidOperationException($"dotnet {arguments} failed with exit code {process.ExitCode}.\n{output}\n{error}"); } public void Dispose() { Directory.Delete(RootPath, recursive: true); } } }