121 lines
5.1 KiB
C#
121 lines
5.1 KiB
C#
using System.Net;
|
|||
|
|
using System.Net.Sockets;
|
||
|
|
using System.Text.Json;
|
||
|
|
using NucleicApp.Services;
|
||
|
|
using NucleicProtocol.Interop;
|
||
|
|
|
||
|
|
namespace NucleicApp.Tests;
|
||
|
|
|
||
|
|
public sealed class RendererCoreTests
|
||
|
|
{
|
||
|
|
[Fact]
|
||
|
|
public void StoreProjectsSessionsApprovalsAndSettingsFromHostMessagesOnly()
|
||
|
|
{
|
||
|
|
using var store = new RendererStore();
|
||
|
|
store.Apply(TestRepository.Fixture("host", "sessionList"));
|
||
|
|
store.Apply(TestRepository.Fixture("host", "approvalRequested"));
|
||
|
|
store.Apply(TestRepository.Fixture("host", "dashboard"));
|
||
|
|
store.Apply(TestRepository.Fixture("host", "settings"));
|
||
|
|
|
||
|
|
var session = Assert.Single(store.Sessions);
|
||
|
|
Assert.Equal("session-1", session.Id);
|
||
|
|
Assert.Equal("awaitingApproval", session.Status);
|
||
|
|
Assert.Equal(1, session.PendingApprovalCount);
|
||
|
|
Assert.Equal("approval-1", Assert.Single(store.PendingApprovals).Id);
|
||
|
|
Assert.NotNull(store.Dashboard);
|
||
|
|
Assert.True(store.Settings!.Value.GetProperty("resolveApprovalsViaCloud").GetBoolean());
|
||
|
|
|
||
|
|
store.Apply(TestRepository.Fixture("host", "approvalResolved"));
|
||
|
|
Assert.Empty(store.PendingApprovals);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task HostConnectionReadsRendezvousAndRequestsInitialStateOnReady()
|
||
|
|
{
|
||
|
|
var directory = Directory.CreateTempSubdirectory("nucleic-renderer-");
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var rendezvousPath = Path.Combine(directory.FullName, "hostd.json");
|
||
|
|
await File.WriteAllTextAsync(rendezvousPath, JsonSerializer.Serialize(
|
||
|
|
ProtocolInteropTests.ValidRendezvous()));
|
||
|
|
using var store = new RendererStore();
|
||
|
|
var client = new FakeProtocolClient();
|
||
|
|
var factory = new FakeProtocolClientFactory(client);
|
||
|
|
await using var connection = new HostConnection(
|
||
|
|
store, Path.Combine(directory.FullName, "identity"), factory,
|
||
|
|
InlineRendererDispatcher.Instance);
|
||
|
|
|
||
|
|
await connection.StartLocalAsync(rendezvousPath);
|
||
|
|
Assert.Equal("host-1", client.Rendezvous?.HostId);
|
||
|
|
client.EmitState("{\"state\":\"ready\",\"hostID\":\"host-1\",\"welcome\":{}}");
|
||
|
|
Assert.Equal(HostConnectionState.Connected, connection.State);
|
||
|
|
|
||
|
|
var tags = client.Intents.Select(json => ProtocolMessage.Parse(json).Tag).ToArray();
|
||
|
|
Assert.Equal(["listSessions", "listDashboard", "listPeers"], tags);
|
||
|
|
|
||
|
|
client.EmitMessage(TestRepository.Fixture("host", "sessionUpdated"));
|
||
|
|
Assert.Equal("session-1", Assert.Single(store.Sessions).Id);
|
||
|
|
}
|
||
|
|
finally { directory.Delete(recursive: true); }
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task HostConnectionKeepsRetryingAfterSynchronousAdmissionFailures()
|
||
|
|
{
|
||
|
|
var directory = Directory.CreateTempSubdirectory("nucleic-reconnect-");
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var rendezvousPath = Path.Combine(directory.FullName, "hostd.json");
|
||
|
|
await File.WriteAllTextAsync(rendezvousPath, JsonSerializer.Serialize(
|
||
|
|
ProtocolInteropTests.ValidRendezvous()));
|
||
|
|
using var store = new RendererStore();
|
||
|
|
var client = new FakeProtocolClient();
|
||
|
|
client.ConnectLocalResults.Enqueue(ProtocolResult.InvalidState);
|
||
|
|
client.ConnectLocalResults.Enqueue(ProtocolResult.InvalidState);
|
||
|
|
client.ConnectLocalResults.Enqueue(ProtocolResult.Ok);
|
||
|
|
await using var connection = new HostConnection(
|
||
|
|
store, Path.Combine(directory.FullName, "identity"),
|
||
|
|
new FakeProtocolClientFactory(client), InlineRendererDispatcher.Instance);
|
||
|
|
|
||
|
|
await connection.StartLocalAsync(rendezvousPath);
|
||
|
|
await WaitUntilAsync(() => client.ConnectLocalCount == 3, TimeSpan.FromSeconds(3));
|
||
|
|
|
||
|
|
Assert.Equal(3, client.ConnectLocalCount);
|
||
|
|
Assert.Equal(HostConnectionState.Reconnecting, connection.State);
|
||
|
|
}
|
||
|
|
finally { directory.Delete(recursive: true); }
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task LauncherAcceptsOnlyALiveLoopbackRendezvous()
|
||
|
|
{
|
||
|
|
var directory = Directory.CreateTempSubdirectory("nucleic-launcher-");
|
||
|
|
var listener = new TcpListener(IPAddress.Loopback, 0);
|
||
|
|
listener.Start();
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var record = ProtocolInteropTests.ValidRendezvous() with
|
||
|
|
{
|
||
|
|
Port = ((IPEndPoint)listener.LocalEndpoint).Port,
|
||
|
|
};
|
||
|
|
var path = Path.Combine(directory.FullName, "hostd.json");
|
||
|
|
await File.WriteAllTextAsync(path, JsonSerializer.Serialize(record));
|
||
|
|
var launcher = new HostdLauncher(new HostdLaunchOptions(
|
||
|
|
Path.Combine(directory.FullName, "must-not-launch.exe"), path));
|
||
|
|
var result = await launcher.EnsureRunningAsync();
|
||
|
|
Assert.Equal(record.Port, result.Port);
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
listener.Stop();
|
||
|
|
directory.Delete(recursive: true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static async Task WaitUntilAsync(Func<bool> condition, TimeSpan timeout)
|
||
|
|
{
|
||
|
|
using var deadline = new CancellationTokenSource(timeout);
|
||
|
|
while (!condition()) await Task.Delay(10, deadline.Token);
|
||
|
|
}
|
||
|
|
}
|