mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-28 23:48:39 +02:00
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
205 lines
6.6 KiB
C#
205 lines
6.6 KiB
C#
using Aryx.AgentHost.Contracts;
|
|
using Aryx.AgentHost.Services;
|
|
|
|
namespace Aryx.AgentHost.Tests;
|
|
|
|
public sealed class HookConfigLoaderTests
|
|
{
|
|
[Fact]
|
|
public async Task LoadAsync_ParsesSupportedHookTypes()
|
|
{
|
|
using TestDirectory project = new();
|
|
string hooksDirectory = Directory.CreateDirectory(Path.Combine(project.Path, ".github", "hooks")).FullName;
|
|
|
|
await File.WriteAllTextAsync(
|
|
Path.Combine(hooksDirectory, "hooks.json"),
|
|
"""
|
|
{
|
|
"version": 1,
|
|
"hooks": {
|
|
"sessionStart": [
|
|
{
|
|
"type": "command",
|
|
"bash": "echo session-start",
|
|
"powershell": "Write-Output session-start",
|
|
"cwd": ".",
|
|
"env": { "HOOK_MODE": "audit" },
|
|
"timeoutSec": 15
|
|
}
|
|
],
|
|
"preToolUse": [
|
|
{
|
|
"type": "command",
|
|
"bash": "echo pre-tool",
|
|
"powershell": "Write-Output pre-tool"
|
|
}
|
|
],
|
|
"errorOccurred": [
|
|
{
|
|
"type": "command",
|
|
"bash": "echo error-hook",
|
|
"powershell": "Write-Output error-hook"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
""");
|
|
|
|
ResolvedHookSet hooks = await HookConfigLoader.LoadAsync(project.Path, CancellationToken.None);
|
|
|
|
HookCommandDefinition sessionStart = Assert.Single(hooks.SessionStart);
|
|
Assert.Equal("command", sessionStart.Type);
|
|
Assert.Equal("echo session-start", sessionStart.Bash);
|
|
Assert.Equal("Write-Output session-start", sessionStart.PowerShell);
|
|
Assert.Equal(".", sessionStart.Cwd);
|
|
Assert.Equal(15, sessionStart.TimeoutSec);
|
|
Assert.NotNull(sessionStart.Env);
|
|
Assert.Equal("audit", sessionStart.Env["HOOK_MODE"]);
|
|
Assert.Single(hooks.PreToolUse);
|
|
Assert.Single(hooks.ErrorOccurred);
|
|
Assert.False(hooks.IsEmpty);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LoadAsync_MergesHookFilesInFileNameOrder()
|
|
{
|
|
using TestDirectory project = new();
|
|
string hooksDirectory = Directory.CreateDirectory(Path.Combine(project.Path, ".github", "hooks")).FullName;
|
|
|
|
await File.WriteAllTextAsync(
|
|
Path.Combine(hooksDirectory, "20-second.json"),
|
|
"""
|
|
{
|
|
"version": 1,
|
|
"hooks": {
|
|
"preToolUse": [
|
|
{
|
|
"type": "command",
|
|
"bash": "echo second",
|
|
"powershell": "Write-Output second"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
""");
|
|
await File.WriteAllTextAsync(
|
|
Path.Combine(hooksDirectory, "10-first.json"),
|
|
"""
|
|
{
|
|
"version": 1,
|
|
"hooks": {
|
|
"preToolUse": [
|
|
{
|
|
"type": "command",
|
|
"bash": "echo first",
|
|
"powershell": "Write-Output first"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
""");
|
|
|
|
ResolvedHookSet hooks = await HookConfigLoader.LoadAsync(project.Path, CancellationToken.None);
|
|
|
|
string[] commands = hooks.PreToolUse.Select(GetCommandText).ToArray();
|
|
Assert.Equal(2, commands.Length);
|
|
Assert.Contains("first", commands[0], StringComparison.Ordinal);
|
|
Assert.Contains("second", commands[1], StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LoadAsync_ReturnsEmptyWhenHooksDirectoryIsMissing()
|
|
{
|
|
using TestDirectory project = new();
|
|
|
|
ResolvedHookSet hooks = await HookConfigLoader.LoadAsync(project.Path, CancellationToken.None);
|
|
|
|
Assert.Same(ResolvedHookSet.Empty, hooks);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LoadAsync_SkipsInvalidFilesAndUnsupportedVersions()
|
|
{
|
|
using TestDirectory project = new();
|
|
string hooksDirectory = Directory.CreateDirectory(Path.Combine(project.Path, ".github", "hooks")).FullName;
|
|
|
|
await File.WriteAllTextAsync(Path.Combine(hooksDirectory, "00-invalid.json"), "{ not-json");
|
|
await File.WriteAllTextAsync(
|
|
Path.Combine(hooksDirectory, "10-unsupported.json"),
|
|
"""
|
|
{
|
|
"version": 2,
|
|
"hooks": {
|
|
"sessionStart": [
|
|
{
|
|
"type": "command",
|
|
"bash": "echo unsupported",
|
|
"powershell": "Write-Output unsupported"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
""");
|
|
await File.WriteAllTextAsync(
|
|
Path.Combine(hooksDirectory, "20-valid.json"),
|
|
"""
|
|
{
|
|
"version": 1,
|
|
"hooks": {
|
|
"sessionEnd": [
|
|
{
|
|
"type": "command",
|
|
"bash": "echo valid",
|
|
"powershell": "Write-Output valid"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
""");
|
|
|
|
ResolvedHookSet hooks = await HookConfigLoader.LoadAsync(project.Path, CancellationToken.None);
|
|
|
|
HookCommandDefinition valid = Assert.Single(hooks.SessionEnd);
|
|
Assert.Contains("valid", GetCommandText(valid), StringComparison.Ordinal);
|
|
Assert.Empty(hooks.SessionStart);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LoadAsync_ReturnsEmptyForEmptyHooksObject()
|
|
{
|
|
using TestDirectory project = new();
|
|
string hooksDirectory = Directory.CreateDirectory(Path.Combine(project.Path, ".github", "hooks")).FullName;
|
|
|
|
await File.WriteAllTextAsync(
|
|
Path.Combine(hooksDirectory, "hooks.json"),
|
|
"""
|
|
{
|
|
"version": 1,
|
|
"hooks": {}
|
|
}
|
|
""");
|
|
|
|
ResolvedHookSet hooks = await HookConfigLoader.LoadAsync(project.Path, CancellationToken.None);
|
|
|
|
Assert.Same(ResolvedHookSet.Empty, hooks);
|
|
}
|
|
|
|
private static string GetCommandText(HookCommandDefinition hook)
|
|
=> hook.PowerShell ?? hook.Bash ?? string.Empty;
|
|
|
|
private sealed class TestDirectory : IDisposable
|
|
{
|
|
private readonly DirectoryInfo _directory = Directory.CreateTempSubdirectory("aryx-hooks-loader-");
|
|
|
|
public string Path => _directory.FullName;
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_directory.Exists)
|
|
{
|
|
_directory.Delete(recursive: true);
|
|
}
|
|
}
|
|
}
|
|
}
|