using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Streaming; using ErsatzTV.Core.Streaming; using NUnit.Framework; using Shouldly; namespace ErsatzTV.Core.Tests.Streaming; [TestFixture] public class DirectStreamSessionTrackerTests { [Test] public void Should_Track_Concurrent_Viewers_Per_Channel() { var tracker = new DirectStreamSessionTracker(); using IDisposable session1 = tracker.Register("1", StreamingMode.TransportStream); using IDisposable session2 = tracker.Register("1", StreamingMode.HttpLiveStreamingDirect); using IDisposable session3 = tracker.Register("2", StreamingMode.TransportStream); tracker.IsActive("1").ShouldBeTrue(); tracker.GetViewerCount("1").ShouldBe(2); tracker.GetViewerCount("2").ShouldBe(1); tracker.GetActiveSessions().Count.ShouldBe(3); } [Test] public void Should_Remove_Only_Disposed_Session() { var tracker = new DirectStreamSessionTracker(); IDisposable session1 = tracker.Register("1", StreamingMode.TransportStream); IDisposable session2 = tracker.Register("1", StreamingMode.TransportStream); session1.Dispose(); tracker.IsActive("1").ShouldBeTrue(); tracker.GetViewerCount("1").ShouldBe(1); session2.Dispose(); tracker.IsActive("1").ShouldBeFalse(); tracker.GetViewerCount("1").ShouldBe(0); } [Test] public void Should_Dispose_Registration_Only_Once() { var tracker = new DirectStreamSessionTracker(); IDisposable session = tracker.Register("1", StreamingMode.TransportStream); session.Dispose(); session.Dispose(); tracker.IsActive("1").ShouldBeFalse(); tracker.GetViewerCount("1").ShouldBe(0); } [Test] public void Should_Filter_Active_Sessions_By_Channel() { var tracker = new DirectStreamSessionTracker(); using IDisposable session1 = tracker.Register("1", StreamingMode.TransportStream); using IDisposable session2 = tracker.Register("2", StreamingMode.HttpLiveStreamingDirect); IReadOnlyCollection sessions = tracker.GetActiveSessions("2"); sessions.Count.ShouldBe(1); sessions.Single().ChannelNumber.ShouldBe("2"); sessions.Single().StreamingMode.ShouldBe(StreamingMode.HttpLiveStreamingDirect); } [Test] public void Should_Not_Orphan_Session_When_Last_Registration_Is_Removed_During_Register() { DirectStreamSessionTracker tracker = null; IDisposable existingSession = null; tracker = new TestDirectStreamSessionTracker(() => existingSession?.Dispose()); existingSession = tracker.Register("1", StreamingMode.TransportStream); using IDisposable newSession = tracker.Register("1", StreamingMode.HttpLiveStreamingDirect); tracker.IsActive("1").ShouldBeTrue(); tracker.GetViewerCount("1").ShouldBe(1); IReadOnlyCollection sessions = tracker.GetActiveSessions("1"); sessions.Count.ShouldBe(1); sessions.Single().StreamingMode.ShouldBe(StreamingMode.HttpLiveStreamingDirect); } private sealed class TestDirectStreamSessionTracker(Action onRegisteringSession) : DirectStreamSessionTracker { protected override void OnRegisteringSession() => onRegisteringSession(); } }