Implement autostart functionality, ZIP packaging, and related installation scripts; update documentation and tests
This commit is contained in:
+54
-1
@@ -30,6 +30,19 @@ void Check(bool condition, string name)
|
||||
}
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var startupExe = Path.Combine(AppContext.BaseDirectory, "MusicBridge.Agent.exe");
|
||||
var startupRegistry = @"Software\MusicBridge\Tests\" + Guid.NewGuid().ToString("N");
|
||||
try
|
||||
{
|
||||
var startup = new MusicBridge.Agent.Desktop.StartupSettings(startupExe, true, 18765, startupRegistry);
|
||||
Check(startup.Command == $"\"{startupExe}\" --tray --minimized --lan --port 18765", "autostart preserves quoted path, mode and port");
|
||||
Check(!startup.Enabled, "autostart starts disabled");
|
||||
startup.SetEnabled(true);
|
||||
Check(startup.Enabled, "autostart can be enabled in isolated registry key");
|
||||
startup.SetEnabled(false);
|
||||
Check(!startup.Enabled, "autostart can be disabled");
|
||||
}
|
||||
finally { Microsoft.Win32.Registry.CurrentUser.DeleteSubKeyTree(startupRegistry, false); }
|
||||
var invitation = ConnectionInvitation.Create(new Uri("https://192.168.1.10:8765"), new string('A', 64), new("01234567", now.AddMinutes(5)), now);
|
||||
Check(invitation.Version == 1 && invitation.Code == "01234567", "invitation preserves leading zero in pairing code");
|
||||
var encodedInvitation = invitation.ToQrPayload().Split("data=")[1].Replace('-', '+').Replace('_', '/');
|
||||
@@ -329,6 +342,42 @@ await using (var eventServer = new RemoteServer(eventMedia, eventPairing, certif
|
||||
}
|
||||
Check(eventMedia.SubscriberCount == 0, "server unsubscribes from media events on shutdown");
|
||||
|
||||
var recoveryMedia = new FakeMedia { FailRead = true };
|
||||
var recoveryPairing = new PairingService();
|
||||
var recoveryToken = recoveryPairing.Pair(recoveryPairing.Open().Code).Token;
|
||||
await using (var recoveryServer = new RemoteServer(recoveryMedia, recoveryPairing, certificate, false, 0, TimeSpan.FromMilliseconds(50)))
|
||||
{
|
||||
await recoveryServer.StartAsync();
|
||||
Check(recoveryServer.Latest.MediaError is not null, "initial media failure does not prevent server startup");
|
||||
recoveryMedia.FailRead = false;
|
||||
recoveryMedia.Notify();
|
||||
using var recoveredTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (recoveryServer.Latest.MediaError is not null) await Task.Delay(20, recoveredTimeout.Token);
|
||||
Check(recoveryServer.Latest.HasSession, "polling recovers after initial media failure");
|
||||
recoveryMedia.FailRead = true;
|
||||
recoveryMedia.Notify();
|
||||
while (recoveryServer.Latest.MediaError is null) await Task.Delay(20, recoveredTimeout.Token);
|
||||
Check(!recoveryServer.Latest.HasSession && recoveryServer.Latest.ArtworkId is null, "read failure clears stale playing state");
|
||||
recoveryMedia.FailRead = false;
|
||||
recoveryMedia.Title = "Recovered track";
|
||||
recoveryMedia.Notify();
|
||||
while (recoveryServer.Latest.Title != "Recovered track") await Task.Delay(20, recoveredTimeout.Token);
|
||||
Check(recoveryServer.Latest.MediaError is null, "polling survives later transient exception");
|
||||
var address = new UriBuilder(recoveryServer.Addresses.Single()) { Scheme = "wss", Path = "/v1/events" }.Uri;
|
||||
for (var i = 0; i < 12; i++)
|
||||
{
|
||||
using var clientSocket = new ClientWebSocket();
|
||||
clientSocket.Options.SetRequestHeader("Authorization", $"Bearer {recoveryToken}");
|
||||
clientSocket.Options.RemoteCertificateValidationCallback = (_, cert, _, _) => cert?.GetCertHashString(HashAlgorithmName.SHA256) == fingerprint;
|
||||
await clientSocket.ConnectAsync(address, recoveredTimeout.Token);
|
||||
var data = new byte[8192];
|
||||
await clientSocket.ReceiveAsync(data.AsMemory(), recoveredTimeout.Token);
|
||||
clientSocket.Abort();
|
||||
await Task.Delay(30, recoveredTimeout.Token);
|
||||
}
|
||||
Check(true, "abrupt WSS disconnects release slots for reconnection");
|
||||
}
|
||||
|
||||
if (args.Contains("--live-read"))
|
||||
{
|
||||
var manager = await Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
|
||||
@@ -357,13 +406,15 @@ if (args.Contains("--desktop-smoke"))
|
||||
};
|
||||
var exited = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using (var host = new MusicBridge.Agent.Desktop.DesktopHost(() => desktopState, desktopMedia, desktopPairing,
|
||||
() => "Домашняя сеть • https://192.168.1.10:8765", () => { }, () => exited.TrySetResult(), visible: false))
|
||||
() => "Домашняя сеть • https://192.168.1.10:8765", () => { }, () => exited.TrySetResult(), visible: false, minimized: true))
|
||||
{
|
||||
await host.StartAsync().WaitAsync(TimeSpan.FromSeconds(8));
|
||||
var field = typeof(MusicBridge.Agent.Desktop.DesktopHost).GetField("window", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!;
|
||||
var view = (MusicBridge.Agent.Desktop.AgentWindow)field.GetValue(host)!;
|
||||
view.Invoke(() =>
|
||||
{
|
||||
Check(!view.Visible, "minimized startup leaves main window hidden");
|
||||
view.Show();
|
||||
view.RefreshState();
|
||||
using var frame = new Bitmap(view.Width, view.Height);
|
||||
view.DrawToBitmap(frame, new Rectangle(0, 0, frame.Width, frame.Height));
|
||||
@@ -456,6 +507,7 @@ sealed class TestClock(DateTimeOffset now) : TimeProvider
|
||||
|
||||
sealed class FakeMedia : IMediaService
|
||||
{
|
||||
public volatile bool FailRead;
|
||||
public event Action? Changed;
|
||||
public int SubscriberCount => Changed?.GetInvocationList().Length ?? 0;
|
||||
public int ReadCount;
|
||||
@@ -467,6 +519,7 @@ sealed class FakeMedia : IMediaService
|
||||
public Task<MediaState> ReadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Interlocked.Increment(ref ReadCount);
|
||||
if (FailRead) throw new IOException("Simulated player disconnection");
|
||||
return Task.FromResult(new MediaState
|
||||
{
|
||||
HasSession = true, Title = Title, ArtworkId = Artwork.Current?.Id, Volume = 0.5f, Timestamp = DateTimeOffset.UtcNow
|
||||
|
||||
Reference in New Issue
Block a user