Share podcast devices with calls and persist network selection
Reuse camera and microphone preferences across podcast and chat-room pages. Restore available LAN/VPN addresses without persisting disclosure consent, and cover shared preferences and save failures with regression tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
ff1a016c91
commit
377afc711a
8 changed files with 236 additions and 13 deletions
|
|
@ -2,12 +2,103 @@ using Avalonia.Controls;
|
|||
using Avalonia.Headless.XUnit;
|
||||
using PostIt.Services;
|
||||
using PostIt.ViewModels.Chat;
|
||||
using PostIt.ViewModels;
|
||||
using Yavsc.Abstract.Chat.Calls;
|
||||
|
||||
namespace PostIt.Tests;
|
||||
|
||||
public sealed class LanCallViewModelTests
|
||||
{
|
||||
[AvaloniaFact]
|
||||
public async Task Calls_restore_shared_devices_and_persist_choices_and_network_address()
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), $"postit-call-preferences-{Guid.NewGuid():N}.json");
|
||||
var previousDevices = Platform.GetLanCallDevices;
|
||||
try
|
||||
{
|
||||
var settings = new Settings(path)
|
||||
{
|
||||
PodcastCameras = [new("Front", "front"), new("Rear", "rear")],
|
||||
PodcastMicrophones = [new("None", ""), new("USB", "usb"), new("Built-in", "built-in")],
|
||||
PodcastCameraId = "rear", PodcastMicrophoneId = "usb"
|
||||
};
|
||||
await using (var vm = new LanCallViewModel(new Signaling(), settings: settings))
|
||||
{
|
||||
Assert.Equal("rear", vm.Camera?.Value);
|
||||
Assert.Equal("usb", vm.Microphone?.Value);
|
||||
Assert.False(vm.AllowAddressDisclosure);
|
||||
vm.Camera = vm.Cameras[0];
|
||||
vm.Microphone = vm.Microphones[1];
|
||||
Assert.Equal("front", settings.PodcastCameraId);
|
||||
Assert.Equal("built-in", settings.PodcastMicrophoneId);
|
||||
Assert.NotEmpty(vm.Addresses);
|
||||
vm.Address = vm.Addresses.Last();
|
||||
settings.Save();
|
||||
var preferred = vm.Address;
|
||||
Platform.GetLanCallDevices = () => new(
|
||||
[new("Rear", "rear"), new("Front", "front")],
|
||||
[new("Built-in", "built-in"), new("USB", "usb")]);
|
||||
vm.RefreshDevicesCommand.Execute(null);
|
||||
Assert.Equal("front", vm.Camera?.Value);
|
||||
Assert.Equal("built-in", vm.Microphone?.Value);
|
||||
Assert.Equal(preferred, vm.Address);
|
||||
Assert.Contains(settings.PodcastMicrophones, d => d.Value == "");
|
||||
|
||||
var reloaded = new Settings(path);
|
||||
reloaded.Authentication.Authority = "";
|
||||
reloaded.Load();
|
||||
await using var restored = new LanCallViewModel(new Signaling(), settings: reloaded);
|
||||
Assert.Equal(preferred, restored.Address);
|
||||
Assert.Equal("front", restored.Camera?.Value);
|
||||
Assert.Equal("built-in", restored.Microphone?.Value);
|
||||
Assert.False(restored.AllowAddressDisclosure);
|
||||
settings.PodcastCameraId = "rear";
|
||||
Assert.Equal("rear", vm.Camera?.Value);
|
||||
settings.PodcastMicrophoneId = "";
|
||||
Assert.Null(vm.Microphone);
|
||||
vm.AllowAddressDisclosure = true;
|
||||
vm.Room = "General";
|
||||
vm.Target = "bob";
|
||||
await vm.InviteCommand.ExecuteAsync(null);
|
||||
Assert.Contains("microphone", vm.Status);
|
||||
}
|
||||
}
|
||||
finally { Platform.GetLanCallDevices = previousDevices; File.Delete(path); }
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public async Task Call_preference_save_failures_are_reported_without_losing_the_choice()
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), $"postit-call-preferences-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(path);
|
||||
try
|
||||
{
|
||||
var settings = new Settings(path);
|
||||
await using var vm = new LanCallViewModel(new Signaling(), settings: settings);
|
||||
vm.Address = null;
|
||||
vm.Address = "127.0.0.1";
|
||||
Assert.Equal("127.0.0.1", settings.LanCallAddress);
|
||||
Assert.Contains("sauvegarde impossible", vm.Status);
|
||||
Assert.True(settings.IsDirty);
|
||||
}
|
||||
finally { Directory.Delete(path); }
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public async Task Missing_saved_network_address_requires_a_new_selection_and_does_not_restore_consent()
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), $"postit-call-preferences-{Guid.NewGuid():N}.json");
|
||||
try
|
||||
{
|
||||
var settings = new Settings(path) { LanCallAddress = "not-a-local-address" };
|
||||
await using var vm = new LanCallViewModel(new Signaling(), settings: settings);
|
||||
Assert.Null(vm.Address);
|
||||
Assert.False(vm.AllowAddressDisclosure);
|
||||
Assert.Equal("not-a-local-address", settings.LanCallAddress);
|
||||
}
|
||||
finally { File.Delete(path); }
|
||||
}
|
||||
|
||||
private static CallSession Session() => new(Guid.NewGuid(), "General", "alice", "bob", false,
|
||||
DateTimeOffset.UtcNow.AddMinutes(1), CallTransports.LanSrtp);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ using Avalonia.Headless.XUnit;
|
|||
using PostIt.Services;
|
||||
using PostIt.ViewModels;
|
||||
using Yavsc.Api.Client;
|
||||
using Yavsc.Abstract.Identity;
|
||||
using Yavsc.Abstract.Streaming;
|
||||
|
||||
namespace PostIt.Tests;
|
||||
|
||||
|
|
@ -37,13 +39,18 @@ public sealed class PodcastPreferencesTests
|
|||
Assert.Equal("rear", restored.PodcastCameraId);
|
||||
Assert.Equal("", restored.PodcastMicrophoneId);
|
||||
Platform.GetPodcastCaptureDevices = () => throw new InvalidOperationException("Must use saved devices.");
|
||||
await using var otherApi = new YavscApiClient(restored, new TokenStore(path + ".tokens"));
|
||||
await using var otherApi = new PreferencesApi(restored, new TokenStore(path + ".tokens"));
|
||||
var other = new PodcastsPageViewModel(new PodcastApiClient(otherApi, () => restored.ApiUrl), otherApi, session);
|
||||
Assert.Equal(2, other.Cameras.Count);
|
||||
Assert.Equal(2, other.Microphones.Count);
|
||||
Assert.Equal("rear", other.SelectedCamera?.Value);
|
||||
Assert.Equal("", other.SelectedMicrophone?.Value);
|
||||
Assert.Equal(vm.Title, other.Title);
|
||||
restored.PodcastCameraId = "front";
|
||||
restored.PodcastMicrophoneId = "usb";
|
||||
await other.ActivateAsync();
|
||||
Assert.Equal("front", other.SelectedCamera?.Value);
|
||||
Assert.Equal("usb", other.SelectedMicrophone?.Value);
|
||||
|
||||
Platform.GetPodcastCaptureDevices = () => new([], []);
|
||||
other.DetectDevicesCommand.Execute(null);
|
||||
|
|
@ -57,6 +64,17 @@ public sealed class PodcastPreferencesTests
|
|||
File.Delete(path);
|
||||
File.Delete(path + ".tokens");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private sealed class PreferencesApi(Settings settings, TokenStore store) : YavscApiClient(settings, store)
|
||||
{
|
||||
public override Task<T> CallAsync<T>(HttpMethod method, string path, object? body = null, CancellationToken ct = default)
|
||||
{
|
||||
object response = path.EndsWith("/account/me", StringComparison.Ordinal)
|
||||
? new Me { UserId = "owner" } : new List<PodcastResponse>();
|
||||
return Task.FromResult((T)response);
|
||||
}
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
|
|
|
|||
Loading…
Reference in a new issue