using System.Reactive.Linq; using System.Reactive.Subjects; using TeamsISO.Engine.Controller; using TeamsISO.Engine.Domain; using TeamsISO.Engine.Pipeline; namespace TeamsISO.App.Tests.Fakes; // Minimal IIsoController stub for tests that need to instantiate // services in the App layer (ControlSurfaceServer, OscBridge, etc.) // without spinning up the real engine + NDI runtime. // // Everything is a sensible no-op default; tests that need a specific // behaviour (e.g. "EnableIsoAsync was called with these args") subclass // or replace methods via the action hooks. internal sealed class StubIsoController : IIsoController { private readonly BehaviorSubject> _participants = new(Array.Empty()); private readonly BehaviorSubject _alerts = new(default); public IObservable> Participants => _participants; public IObservable Alerts => _alerts.Where(a => a is not null)!; public FrameProcessingSettings GlobalSettings { get; set; } = new( TargetFramerate.Fps30, TargetResolution.R1080p, AspectMode.Letterbox, AudioMode.Auto); public NdiGroupSettings GroupSettings { get; set; } = new( DiscoveryGroups: null, OutputGroups: null); public bool RecordingEnabled { get; private set; } public string? RecordingDirectory { get; private set; } public Func? GetStatsHandler { get; set; } public Func? GetLatestProcessedFrameHandler { get; set; } public Func? GetIsoOverrideHandler { get; set; } public IsoHealthStats GetStats(Guid participantId) => GetStatsHandler?.Invoke(participantId) ?? IsoHealthStats.Empty; public ProcessedFrame? GetLatestProcessedFrame(Guid participantId) => GetLatestProcessedFrameHandler?.Invoke(participantId); public FrameProcessingSettings? GetIsoOverride(Guid participantId) => GetIsoOverrideHandler?.Invoke(participantId); public List<(Guid Id, string? Name)> EnableCalls { get; } = new(); public List DisableCalls { get; } = new(); public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; public Task EnableIsoAsync(Guid participantId, string? customName, CancellationToken cancellationToken) { EnableCalls.Add((participantId, customName)); return Task.CompletedTask; } public Task EnableIsoAsync(Guid participantId, string? customName, bool? recordOverride, CancellationToken cancellationToken) { EnableCalls.Add((participantId, customName)); return Task.CompletedTask; } public Task DisableIsoAsync(Guid participantId, CancellationToken cancellationToken) { DisableCalls.Add(participantId); return Task.CompletedTask; } public Task SetGlobalSettingsAsync(FrameProcessingSettings settings, CancellationToken cancellationToken) { GlobalSettings = settings; return Task.CompletedTask; } public Task SetIsoOverrideAsync(Guid participantId, FrameProcessingSettings? settings, CancellationToken cancellationToken) => Task.CompletedTask; public Task SetGroupSettingsAsync(NdiGroupSettings groupSettings, CancellationToken cancellationToken) { GroupSettings = groupSettings; return Task.CompletedTask; } public bool RefreshDiscoveryCalled { get; private set; } public void RefreshDiscovery() => RefreshDiscoveryCalled = true; public void SetRecording(bool enabled, string? outputDirectory) { RecordingEnabled = enabled; RecordingDirectory = outputDirectory; } public void AddRecordingMarker(string label) { /* no-op for stub */ } public ValueTask DisposeAsync() { _participants.Dispose(); _alerts.Dispose(); return ValueTask.CompletedTask; } // Used by tests to push synthetic participant snapshots through the // observable chain. public void PublishParticipants(params Participant[] participants) => _participants.OnNext(participants); }