Files
Nitload 0ea679932e
Verify / verify (ubuntu-latest, linux-bionic-arm64) (push) Canceled after 0s
Verify / verify (ubuntu-latest, linux-x64) (push) Canceled after 0s
Verify / verify (windows-latest, win-x64) (push) Canceled after 0s
Verify / postgres (push) Canceled after 0s
Build Charac server and client with OIDC, SSH sessions, and packages
2026-10-05 14:02:50 +08:00

100 lines
4.9 KiB
C#

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using Charac.Server.Hosting;
using Charac.Server.Connections;
using Charac.Server.Ssh;
namespace Charac.Server.Tests;
public sealed class ServerControlChannelTests
{
[Fact]
public async Task RunningProcessAcceptsStatusAndLoggingReloadButRejectsServerChange()
{
var directory = Path.Combine(Path.GetTempPath(), "workspace-access-control-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
var path = Path.Combine(directory, "workspace-access.config");
var original = "[server]\ndomain=127.0.0.1\nlisten=http://127.0.0.1:5080\n[Logging:LogLevel]\nDefault=Information\n";
await File.WriteAllTextAsync(path, original, TestContext.Current.CancellationToken);
var live = new ConfigurationBuilder().AddIniFile(path, optional: false, reloadOnChange: false).Build();
using var workspaces = new SshWorkspaceManager();
using var control = new ServerControlChannel(path, live, workspaces, new ActiveClientDevices(),
NullLogger<ServerControlChannel>.Instance);
try
{
await control.StartAsync(TestContext.Current.CancellationToken);
var status = await ServerControlClient.SendAsync("status", path);
Assert.True(status.Ok);
Assert.Equal(Environment.ProcessId, status.ProcessId);
Assert.Equal(0, status.Workspaces);
var sessions = await ServerControlClient.SendAsync("sessions", path);
Assert.True(sessions.Ok);
Assert.Empty(Assert.IsType<ServerControlSession[]>(sessions.Sessions));
var disabled = await ServerControlClient.SendAsync("disconnect alice", path);
Assert.False(disabled.Ok);
Assert.Null(disabled.Disconnected);
var oversized = await ServerControlClient.SendAsync(new string('x', 513), path);
Assert.False(oversized.Ok);
Assert.True((await ServerControlClient.SendAsync("status", path)).Ok);
await File.WriteAllTextAsync(path, original.Replace("Default=Information", "Default=Warning"),
TestContext.Current.CancellationToken);
var reload = await ServerControlClient.SendAsync("reload", path);
Assert.True(reload.Ok);
Assert.Equal("Warning", live["Logging:LogLevel:Default"]);
await File.WriteAllTextAsync(path, original.Replace("5080", "5081"),
TestContext.Current.CancellationToken);
var rejected = await ServerControlClient.SendAsync("reload", path);
Assert.False(rejected.Ok);
Assert.Equal("Warning", live["Logging:LogLevel:Default"]);
Assert.Equal("http://127.0.0.1:5080", live["server:listen"]);
}
finally
{
await control.StopAsync(CancellationToken.None);
Directory.Delete(directory, recursive: true);
}
}
[Fact]
public async Task ExplicitlyEnabledDisconnectUsesConfiguredIssuerAndValidatesSubject()
{
var directory = Path.Combine(Path.GetTempPath(), "workspace-access-control-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
var path = Path.Combine(directory, "workspace-access.config");
await File.WriteAllTextAsync(path,
"[management]\nallow_disconnect=true\n[oidc]\nissuer=https://auth.example/workspace/\n",
TestContext.Current.CancellationToken);
var live = new ConfigurationBuilder().AddIniFile(path, optional: false).Build();
using var workspaces = new SshWorkspaceManager();
var devices = new ActiveClientDevices();
using var control = new ServerControlChannel(path, live, workspaces, devices,
NullLogger<ServerControlChannel>.Instance);
try
{
await control.StartAsync(TestContext.Current.CancellationToken);
var identity = new Charac.Server.Authentication.ExternalIdentity(
"https://auth.example/workspace/", "alice");
var credential = Convert.ToBase64String(System.Security.Cryptography.RandomNumberGenerator.GetBytes(32))
.TrimEnd('=').Replace('+', '-').Replace('/', '_');
Assert.Equal(ClientDeviceStatus.Allowed, devices.Acquire(identity, credential, out var old));
var accepted = await ServerControlClient.SendAsync("disconnect alice", path);
Assert.True(accepted.Ok);
Assert.Equal(0, accepted.Disconnected);
Assert.False(devices.TryUse(old!, () => new object(), out _));
old!.Dispose();
var rejected = await ServerControlClient.SendAsync("disconnect ", path);
Assert.False(rejected.Ok);
}
finally
{
await control.StopAsync(CancellationToken.None);
Directory.Delete(directory, recursive: true);
}
}
}