Files
Playa/tests/Volcanic.AgenticCore.Extensions.Tests/ClaudeCode/ClaudeInstallationTests.cs

110 lines
4.7 KiB
C#

using Volcanic.AgenticCore.Extensions.ClaudeCode;
namespace Volcanic.AgenticCore.Extensions.Tests.ClaudeCode;
public class ClaudeInstallationTests
{
[Fact]
public void Captured_help_has_every_required_flag()
{
Assert.Empty(ClaudeInstallation.MissingFlags(Fixtures.HelpText));
Assert.Contains("--print", ClaudeInstallation.RequiredFlags);
Assert.Contains("--system-prompt", ClaudeInstallation.RequiredFlags);
}
[Fact]
public void Truncated_help_reports_the_missing_flags()
{
var lines = Fixtures.HelpText.Split('\n');
var truncated = string.Join('\n', lines.Take(60));
var missing = ClaudeInstallation.MissingFlags(truncated);
Assert.NotEmpty(missing);
Assert.Contains("--verbose", missing);
Assert.Contains("--tools", missing);
Assert.DoesNotContain("--add-dir", missing);
Assert.Empty(ClaudeInstallation.MissingFlags(" -p, --print x\n" + string.Join('\n', ClaudeInstallation.RequiredFlags.Select(f => $" {f} <v> help"))));
// A longer flag with the same prefix does not satisfy the check.
Assert.Contains("--tools", ClaudeInstallation.MissingFlags(" --toolset <x>"));
}
[Fact]
public void Locate_prefers_the_environment_override_then_searches_path()
{
Assert.Equal("/opt/claude/bin/claude", ClaudeInstallation.Locate(name => name == ClaudeInstallation.PathVariable ? " /opt/claude/bin/claude " : null));
Assert.Null(ClaudeInstallation.Locate(_ => null));
var directory = Directory.CreateTempSubdirectory("locate-test-").FullName;
try
{
var name = OperatingSystem.IsWindows() ? "claude.cmd" : "claude";
var executable = Path.Combine(directory, name);
File.WriteAllText(executable, "#!/bin/sh\nexit 0\n");
if (!OperatingSystem.IsWindows())
{
File.SetUnixFileMode(executable, UnixFileMode.UserRead | UnixFileMode.UserWrite);
Assert.Null(ClaudeInstallation.Locate(n => n == "PATH" ? directory : null));
File.SetUnixFileMode(executable, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
}
Assert.Equal(executable, ClaudeInstallation.Locate(n => n switch
{
"PATH" => $"{Path.Combine(directory, "missing")}{Path.PathSeparator}{directory}",
"PATHEXT" => ".COM;.EXE;.BAT;.CMD",
_ => null,
}));
}
finally
{
Directory.Delete(directory, recursive: true);
}
}
[Fact]
public async Task Inspect_reads_version_auth_and_help_from_the_cli()
{
if (!Fixtures.IsPosix) return;
var directory = Directory.CreateTempSubdirectory("inspect-test-").FullName;
try
{
var script = Path.Combine(directory, "claude");
File.WriteAllText(script, $$"""
#!/bin/sh
case "$1" in
--version) echo "2.1.280 (Claude Code)";;
auth) echo '{"loggedIn":true,"authMethod":"claude.ai","apiProvider":"firstParty","subscriptionType":"Pro"}';;
--help) cat "{{Fixtures.FixturePath("claude-2.1.281-help.txt")}}";;
*) echo "unexpected: $*" >&2; exit 3;;
esac
""");
if (!OperatingSystem.IsWindows()) File.SetUnixFileMode(script, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
var info = await ClaudeInstallation.InspectAsync(script, CancellationToken.None);
Assert.Equal(script, info.Path);
Assert.Equal("2.1.280", info.Version);
Assert.True(info.LoggedIn);
Assert.Equal("claude.ai", info.AuthMethod);
Assert.Equal("firstParty", info.ApiProvider);
Assert.Equal("pro", info.SubscriptionType);
Assert.Empty(info.MissingFlags);
Assert.DoesNotContain(info.Warnings, w => w.Contains("version"));
File.WriteAllText(script, "#!/bin/sh\necho 'claude: not logged in'\nexit 1\n");
var broken = await ClaudeInstallation.InspectAsync(script, CancellationToken.None);
Assert.Null(broken.Version);
Assert.False(broken.LoggedIn);
Assert.Equal(ClaudeInstallation.RequiredFlags.Count, broken.MissingFlags.Count);
Assert.NotEmpty(broken.Warnings);
}
finally
{
Directory.Delete(directory, recursive: true);
}
var missing = await Assert.ThrowsAsync<ClaudeCodeException>(() => ClaudeInstallation.InspectAsync(Path.Combine(directory, "nope"), CancellationToken.None));
Assert.Equal("executable_missing", missing.Code);
}
}