authoring app tests
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<AssemblyName>Covenant.Setup.Authoring.Tests</AssemblyName>
|
||||
<RootNamespace>Covenant.Setup.Authoring.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\authoring-ui\Covenant.Setup.Authoring.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,86 @@
|
||||
using Covenant.Setup.Authoring;
|
||||
using Xunit;
|
||||
|
||||
namespace Covenant.Setup.Authoring.Tests;
|
||||
|
||||
public class CovenantSetupToolTests
|
||||
{
|
||||
[Fact]
|
||||
public void Find_returns_first_existing_covenant_setup_exe()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var missing = Path.Combine(temp.Path, "missing");
|
||||
var found = Path.Combine(temp.Path, "found");
|
||||
Directory.CreateDirectory(missing);
|
||||
Directory.CreateDirectory(found);
|
||||
var exe = Path.Combine(found, "covenant-setup.exe");
|
||||
File.WriteAllText(exe, string.Empty);
|
||||
|
||||
var tool = CovenantSetupToolLocator.Find([missing, found]);
|
||||
|
||||
Assert.NotNull(tool);
|
||||
Assert.Equal(exe, tool.Path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_returns_null_when_no_candidate_contains_exe()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var candidate = Path.Combine(temp.Path, "candidate");
|
||||
Directory.CreateDirectory(candidate);
|
||||
|
||||
var tool = CovenantSetupToolLocator.Find([candidate]);
|
||||
|
||||
Assert.Null(tool);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateStartInfo_builds_expected_package_command()
|
||||
{
|
||||
var tool = new CovenantSetupTool(@"C:\tools\covenant-setup.exe");
|
||||
var manifest = @"C:\src\sample app\install.toml";
|
||||
var output = @"C:\out dir";
|
||||
|
||||
var startInfo = CovenantSetupPackager.CreateStartInfo(tool, manifest, output);
|
||||
|
||||
Assert.Equal(tool.Path, startInfo.FileName);
|
||||
Assert.Equal(@"C:\src\sample app", startInfo.WorkingDirectory);
|
||||
Assert.False(startInfo.UseShellExecute);
|
||||
Assert.True(startInfo.CreateNoWindow);
|
||||
Assert.True(startInfo.RedirectStandardOutput);
|
||||
Assert.True(startInfo.RedirectStandardError);
|
||||
Assert.Equal(["--json", "package", manifest, "--output", output], startInfo.ArgumentList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateStartInfo_uses_current_directory_for_relative_manifest()
|
||||
{
|
||||
var tool = new CovenantSetupTool(@"C:\tools\covenant-setup.exe");
|
||||
|
||||
var startInfo = CovenantSetupPackager.CreateStartInfo(tool, "install.toml", "dist");
|
||||
|
||||
Assert.Equal(Environment.CurrentDirectory, startInfo.WorkingDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TempDirectory : IDisposable
|
||||
{
|
||||
public TempDirectory()
|
||||
{
|
||||
Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "covenant-authoring-tests-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(Path);
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(Path, recursive: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using Covenant.Setup.Authoring;
|
||||
using Xunit;
|
||||
|
||||
namespace Covenant.Setup.Authoring.Tests;
|
||||
|
||||
public class MainViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_populates_default_manifest_entries()
|
||||
{
|
||||
var viewModel = new MainViewModel(() => null);
|
||||
|
||||
Assert.Equal("Covenant-Setup Sample App", viewModel.AppName);
|
||||
Assert.Contains(viewModel.Directories, directory => directory.Path == @"{LocalAppData}\CovenantSetupSample");
|
||||
Assert.Contains(viewModel.Files, file => file.Source == @"payload\sample_app.cmd");
|
||||
Assert.Contains(viewModel.PurgePaths, path => path == @"{LocalAppData}\CovenantSetupSample");
|
||||
Assert.Contains("app_name = \"Covenant-Setup Sample App\"", viewModel.TomlPreview);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_rejects_unsupported_registry_roots()
|
||||
{
|
||||
var viewModel = new MainViewModel(() => null);
|
||||
viewModel.Registry.Clear();
|
||||
viewModel.PurgeRegistryBranches.Clear();
|
||||
viewModel.AddRegistry(@"HKCR\Software\Bad", "Name", "Value");
|
||||
viewModel.AddPurgeRegistryBranch(@"HKU\Software\Bad");
|
||||
|
||||
var validation = viewModel.Validate();
|
||||
|
||||
Assert.False(validation.IsValid);
|
||||
Assert.Contains(validation.Errors, error => error.Contains(@"HKCR\Software\Bad", StringComparison.Ordinal));
|
||||
Assert.Contains(validation.Errors, error => error.Contains(@"HKU\Software\Bad", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_warns_when_manifest_requires_elevation()
|
||||
{
|
||||
var viewModel = new MainViewModel(() => null)
|
||||
{
|
||||
InstallRootToken = "{ProgramFilesX64}"
|
||||
};
|
||||
viewModel.ApplyDefaults(resetCollections: true);
|
||||
|
||||
var validation = viewModel.Validate();
|
||||
|
||||
Assert.True(validation.IsValid);
|
||||
Assert.Contains(validation.Warnings, warning => warning.Contains("require elevation", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Packaging_is_disabled_when_covenant_setup_is_missing()
|
||||
{
|
||||
var viewModel = new MainViewModel(() => null);
|
||||
|
||||
Assert.False(viewModel.HasCovenantSetupTool);
|
||||
Assert.False(viewModel.CanPackage);
|
||||
Assert.Contains("not found", viewModel.CovenantSetupStatus, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Packaging_is_enabled_only_when_tool_and_valid_manifest_are_present()
|
||||
{
|
||||
var viewModel = new MainViewModel(() => new CovenantSetupTool(@"C:\tools\covenant-setup.exe"));
|
||||
|
||||
Assert.True(viewModel.HasCovenantSetupTool);
|
||||
Assert.True(viewModel.CanPackage);
|
||||
|
||||
viewModel.AppName = string.Empty;
|
||||
|
||||
Assert.True(viewModel.HasValidationErrors);
|
||||
Assert.False(viewModel.CanPackage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using Covenant.Setup.Authoring;
|
||||
using Xunit;
|
||||
|
||||
namespace Covenant.Setup.Authoring.Tests;
|
||||
|
||||
public class ManifestTomlWriterTests
|
||||
{
|
||||
[Fact]
|
||||
public void Write_emits_current_manifest_schema()
|
||||
{
|
||||
var document = new ManifestDocument
|
||||
{
|
||||
AppName = "Sample App",
|
||||
Directories =
|
||||
[
|
||||
new DirectorySpec(@"{LocalAppData}\Sample")
|
||||
],
|
||||
Files =
|
||||
[
|
||||
new FileSpec(@"payload\app.exe", @"{LocalAppData}\Sample\bin\app.exe")
|
||||
],
|
||||
Registry =
|
||||
[
|
||||
new RegistrySpec(@"HKCU\Software\Sample", "InstallRoot", @"{LocalAppData}\Sample")
|
||||
],
|
||||
Shortcuts =
|
||||
[
|
||||
new ShortcutSpec(
|
||||
@"{Desktop}\Sample App.lnk",
|
||||
@"{LocalAppData}\Sample\bin\app.exe",
|
||||
"--profile default",
|
||||
@"{LocalAppData}\Sample",
|
||||
"Launch Sample App")
|
||||
],
|
||||
Scripts =
|
||||
[
|
||||
new ScriptSpec(
|
||||
"powershell",
|
||||
["-ExecutionPolicy", "Bypass", "-File", @"payload\post_install.ps1"],
|
||||
@"{LocalAppData}\Sample")
|
||||
],
|
||||
Purge = new PurgeSpec
|
||||
{
|
||||
RegistryBranches = [@"HKCU\Software\Sample"],
|
||||
Paths = [@"{LocalAppData}\Sample"]
|
||||
}
|
||||
};
|
||||
|
||||
var toml = Normalize(ManifestTomlWriter.Write(document));
|
||||
|
||||
const string expected = """
|
||||
app_name = "Sample App"
|
||||
|
||||
[[directories]]
|
||||
path = "{LocalAppData}\\Sample"
|
||||
|
||||
[[files]]
|
||||
source = "payload\\app.exe"
|
||||
destination = "{LocalAppData}\\Sample\\bin\\app.exe"
|
||||
|
||||
[[registry]]
|
||||
key = "HKCU\\Software\\Sample"
|
||||
name = "InstallRoot"
|
||||
value = "{LocalAppData}\\Sample"
|
||||
|
||||
[[shortcuts]]
|
||||
path = "{Desktop}\\Sample App.lnk"
|
||||
target = "{LocalAppData}\\Sample\\bin\\app.exe"
|
||||
arguments = "--profile default"
|
||||
working_directory = "{LocalAppData}\\Sample"
|
||||
description = "Launch Sample App"
|
||||
|
||||
[[scripts]]
|
||||
command = "powershell"
|
||||
args = [
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"payload\\post_install.ps1"
|
||||
]
|
||||
working_directory = "{LocalAppData}\\Sample"
|
||||
|
||||
[purge]
|
||||
registry_branches = ["HKCU\\Software\\Sample"]
|
||||
paths = ["{LocalAppData}\\Sample"]
|
||||
|
||||
""";
|
||||
Assert.Equal(Normalize(expected), toml);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Write_escapes_toml_strings()
|
||||
{
|
||||
var document = new ManifestDocument
|
||||
{
|
||||
AppName = "Quoted \"App\"\nTabbed\tPath",
|
||||
Directories = [new DirectorySpec(@"C:\Apps\Sample")],
|
||||
Purge = new PurgeSpec()
|
||||
};
|
||||
|
||||
var toml = Normalize(ManifestTomlWriter.Write(document));
|
||||
|
||||
Assert.Contains("app_name = \"Quoted \\\"App\\\"\\nTabbed\\tPath\"", toml);
|
||||
Assert.Contains("path = \"C:\\\\Apps\\\\Sample\"", toml);
|
||||
}
|
||||
|
||||
private static string Normalize(string value) => value.Replace("\r\n", "\n");
|
||||
}
|
||||
@@ -18,4 +18,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Covenant.Setup.Authoring.Tests" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -15,7 +15,12 @@ internal static class CovenantSetupToolLocator
|
||||
{
|
||||
public static CovenantSetupTool? Find()
|
||||
{
|
||||
foreach (var candidate in CandidatePaths())
|
||||
return Find(CandidateDirectories());
|
||||
}
|
||||
|
||||
internal static CovenantSetupTool? Find(IEnumerable<string> candidateDirectories)
|
||||
{
|
||||
foreach (var candidate in CandidatePaths(candidateDirectories))
|
||||
{
|
||||
if (File.Exists(candidate))
|
||||
{
|
||||
@@ -26,10 +31,10 @@ internal static class CovenantSetupToolLocator
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> CandidatePaths()
|
||||
private static IEnumerable<string> CandidatePaths(IEnumerable<string> candidateDirectories)
|
||||
{
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var directory in CandidateDirectories())
|
||||
foreach (var directory in candidateDirectories)
|
||||
{
|
||||
var candidate = System.IO.Path.Combine(directory, "covenant-setup.exe");
|
||||
if (seen.Add(candidate))
|
||||
@@ -91,22 +96,9 @@ internal static class CovenantSetupPackager
|
||||
var error = new StringBuilder();
|
||||
using var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = tool.Path,
|
||||
WorkingDirectory = System.IO.Path.GetDirectoryName(manifestPath) ?? Environment.CurrentDirectory,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
},
|
||||
StartInfo = CreateStartInfo(tool, manifestPath, outputDirectory),
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
process.StartInfo.ArgumentList.Add("--json");
|
||||
process.StartInfo.ArgumentList.Add("package");
|
||||
process.StartInfo.ArgumentList.Add(manifestPath);
|
||||
process.StartInfo.ArgumentList.Add("--output");
|
||||
process.StartInfo.ArgumentList.Add(outputDirectory);
|
||||
|
||||
process.OutputDataReceived += (_, args) =>
|
||||
{
|
||||
@@ -133,4 +125,29 @@ internal static class CovenantSetupPackager
|
||||
output.ToString().Trim(),
|
||||
error.ToString().Trim());
|
||||
}
|
||||
|
||||
internal static ProcessStartInfo CreateStartInfo(
|
||||
CovenantSetupTool tool,
|
||||
string manifestPath,
|
||||
string outputDirectory)
|
||||
{
|
||||
var manifestDirectory = System.IO.Path.GetDirectoryName(manifestPath);
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = tool.Path,
|
||||
WorkingDirectory = string.IsNullOrWhiteSpace(manifestDirectory)
|
||||
? Environment.CurrentDirectory
|
||||
: manifestDirectory,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
startInfo.ArgumentList.Add("--json");
|
||||
startInfo.ArgumentList.Add("package");
|
||||
startInfo.ArgumentList.Add(manifestPath);
|
||||
startInfo.ArgumentList.Add("--output");
|
||||
startInfo.ArgumentList.Add(outputDirectory);
|
||||
return startInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,16 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
private string _tomlPreview = string.Empty;
|
||||
private string _validationSummary = string.Empty;
|
||||
private bool _hasValidationErrors;
|
||||
private readonly Func<CovenantSetupTool?> _locateCovenantSetupTool;
|
||||
|
||||
public MainViewModel()
|
||||
: this(CovenantSetupToolLocator.Find)
|
||||
{
|
||||
}
|
||||
|
||||
internal MainViewModel(Func<CovenantSetupTool?> locateCovenantSetupTool)
|
||||
{
|
||||
_locateCovenantSetupTool = locateCovenantSetupTool;
|
||||
Directories.CollectionChanged += CollectionChanged;
|
||||
Files.CollectionChanged += CollectionChanged;
|
||||
Registry.CollectionChanged += CollectionChanged;
|
||||
@@ -170,7 +177,7 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
|
||||
public void RefreshCovenantSetupTool()
|
||||
{
|
||||
CovenantSetupTool = CovenantSetupToolLocator.Find();
|
||||
CovenantSetupTool = _locateCovenantSetupTool();
|
||||
CovenantSetupStatus = CovenantSetupTool is null
|
||||
? "covenant-setup.exe was not found. Packaging is disabled."
|
||||
: "Packaging enabled: " + CovenantSetupTool.Path;
|
||||
|
||||
@@ -8,6 +8,12 @@ Run from the repository root:
|
||||
dotnet run --project .\authoring-ui\Covenant.Setup.Authoring.csproj
|
||||
```
|
||||
|
||||
Run unit tests from the repository root:
|
||||
|
||||
```powershell
|
||||
dotnet test .\authoring-ui.Tests\Covenant.Setup.Authoring.Tests.csproj
|
||||
```
|
||||
|
||||
The app edits the current manifest schema:
|
||||
|
||||
- `app_name`
|
||||
|
||||
Reference in New Issue
Block a user