feat(headless): establish portable Linux host boundary
Add the presentation-free acdream-headless executable, strict no-connect configuration validation, dependency and assembly guards, and a Windows/Ubuntu CI lane that builds and tests only the portable runtime closure. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
953c469cac
commit
aada8a37c1
14 changed files with 613 additions and 8 deletions
109
tests/AcDream.Headless.Tests/HeadlessEntryPointTests.cs
Normal file
109
tests/AcDream.Headless.Tests/HeadlessEntryPointTests.cs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
namespace AcDream.Headless.Tests;
|
||||
|
||||
public sealed class HeadlessEntryPointTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("--help")]
|
||||
[InlineData("-h")]
|
||||
public void HelpIsPresentationFreeAndSuccessful(string argument)
|
||||
{
|
||||
using var output = new StringWriter();
|
||||
using var error = new StringWriter();
|
||||
|
||||
int exitCode = HeadlessEntryPoint.Run(
|
||||
[argument],
|
||||
output,
|
||||
error);
|
||||
|
||||
Assert.Equal(0, exitCode);
|
||||
Assert.Contains("acdream-headless", output.ToString());
|
||||
Assert.Contains("Secrets are never accepted", output.ToString());
|
||||
Assert.Equal(string.Empty, error.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateAcceptsStrictEmptyNoConnectConfiguration()
|
||||
{
|
||||
using var file = TemporaryConfiguration.Create(
|
||||
"""{"version":1,"sessions":[]}""");
|
||||
using var output = new StringWriter();
|
||||
using var error = new StringWriter();
|
||||
|
||||
int exitCode = HeadlessEntryPoint.Run(
|
||||
["validate", "--config", file.Path],
|
||||
output,
|
||||
error);
|
||||
|
||||
Assert.Equal(0, exitCode);
|
||||
Assert.Contains("0 session(s)", output.ToString());
|
||||
Assert.Equal(string.Empty, error.ToString());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("""{"version":2,"sessions":[]}""", "Unsupported configuration version")]
|
||||
[InlineData("""{"version":1,"sessions":[],"password":"secret"}""", "could not be mapped")]
|
||||
[InlineData("""{"version":1,"sessions":[{"id":"bot"},{"id":"bot"}]}""", "Duplicate session id")]
|
||||
[InlineData("""{"version":1,"sessions":[null]}""", "non-empty id")]
|
||||
[InlineData("""{"version":1}""", "required properties")]
|
||||
public void ValidateRejectsInvalidOrSecretShapedConfiguration(
|
||||
string json,
|
||||
string expectedError)
|
||||
{
|
||||
using var file = TemporaryConfiguration.Create(json);
|
||||
using var output = new StringWriter();
|
||||
using var error = new StringWriter();
|
||||
|
||||
int exitCode = HeadlessEntryPoint.Run(
|
||||
["validate", "--config", file.Path],
|
||||
output,
|
||||
error);
|
||||
|
||||
Assert.Equal(3, exitCode);
|
||||
Assert.Contains(
|
||||
expectedError,
|
||||
error.ToString(),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("secret", error.ToString());
|
||||
Assert.Equal(string.Empty, output.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownCommandReturnsUsageError()
|
||||
{
|
||||
using var output = new StringWriter();
|
||||
using var error = new StringWriter();
|
||||
|
||||
int exitCode = HeadlessEntryPoint.Run(
|
||||
["connect", "--password", "secret"],
|
||||
output,
|
||||
error);
|
||||
|
||||
Assert.Equal(2, exitCode);
|
||||
Assert.DoesNotContain("secret", error.ToString());
|
||||
Assert.Equal(string.Empty, output.ToString());
|
||||
}
|
||||
|
||||
private sealed class TemporaryConfiguration : IDisposable
|
||||
{
|
||||
private TemporaryConfiguration(string path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
internal string Path { get; }
|
||||
|
||||
internal static TemporaryConfiguration Create(string json)
|
||||
{
|
||||
string path = System.IO.Path.Combine(
|
||||
System.IO.Path.GetTempPath(),
|
||||
$"acdream-headless-{Guid.NewGuid():N}.json");
|
||||
File.WriteAllText(path, json);
|
||||
return new TemporaryConfiguration(path);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
File.Delete(Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue