using Xunit; using ytLive.Services; namespace ytLive.Tests; public class CameraManagerTests { private sealed class FakeFrameSource : ICameraFrameSource { private readonly List _started; private readonly List _stopped; public string DeviceId { get; } public event Action? FrameAvailable; public FakeFrameSource(string deviceId, List started, List stopped) { DeviceId = deviceId; _started = started; _stopped = stopped; } public Task StartAsync() { _started.Add(DeviceId); return Task.CompletedTask; } public Task StopAsync() { _stopped.Add(DeviceId); return Task.CompletedTask; } public void Pump(VideoFrame frame) => FrameAvailable?.Invoke(frame); } private sealed class FailingFrameSource : ICameraFrameSource { public string DeviceId { get; } public event Action? FrameAvailable; public bool Stopped; public FailingFrameSource(string deviceId) => DeviceId = deviceId; public Task StartAsync() => throw new InvalidOperationException("camera in use"); public Task StopAsync() { Stopped = true; return Task.CompletedTask; } public void Pump(VideoFrame frame) => FrameAvailable?.Invoke(frame); } private static CameraManager CreateManager(List started, List stopped) => new(new FakeEnumerator(), id => new FakeFrameSource(id, started, stopped)); private sealed class FakeEnumerator : ICameraEnumerator { public Task> GetCamerasAsync() => Task.FromResult>(new[] { new CameraDeviceInfo("dev1", "Logitech C920"), }); } [Fact] public async Task Refcount_TwoAcquires_OneCaptureUntilLastRelease() { var started = new List(); var stopped = new List(); var manager = CreateManager(started, stopped); Assert.True(await manager.AcquireAsync("dev1")); Assert.True(await manager.AcquireAsync("dev1")); Assert.Single(started); await manager.ReleaseAsync("dev1"); Assert.Empty(stopped); await manager.ReleaseAsync("dev1"); Assert.Single(stopped); Assert.Null(manager.GetLatestFrame("dev1")); } [Fact] public async Task FramePump_UpdatesLatestFrame() { var started = new List(); var stopped = new List(); FakeFrameSource? captured = null; var manager = new CameraManager( new FakeEnumerator(), id => captured = new FakeFrameSource(id, started, stopped)); await manager.AcquireAsync("dev1"); var frame = new VideoFrame(2, 2, new byte[16]); captured!.Pump(frame); Assert.Same(frame, manager.GetLatestFrame("dev1")); } [Fact] public async Task Acquire_FailingCamera_ReturnsFalseAndStops() { var manager = new CameraManager(new FakeEnumerator(), id => new FailingFrameSource(id)); string? failedDevice = null; manager.CameraFailed += (device, _) => failedDevice = device; Assert.False(await manager.AcquireAsync("dev1")); Assert.Equal("dev1", failedDevice); } [Fact] public async Task Acquire_EmptyDeviceId_ReturnsFalse() { var manager = CreateManager(new List(), new List()); Assert.False(await manager.AcquireAsync(" ")); } }