using System.Reflection;
using System.Security.Claims;
using Bunit;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.DebugView;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
using ZB.MOM.WW.ScadaBridge.Communication;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
using DebugViewPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Deployment.DebugView;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Deployment;
///
/// Regression tests for arch-review 2026-08-19 P6-6. Connect() awaits
/// StartStreamAsync — which can take up to 30s — and then publishes the result into
/// _session. If the circuit was torn down inside that window, Dispose() had
/// already run, found _session still null and stopped nothing, so the just-created
/// session stayed registered in the singleton . Its
/// keepalive renews registered sessions forever (the orphan reaper only collects
/// UNregistered ones), so the site subscription, the bridge actor and the dead circuit's
/// object graph were pinned until process restart. The fix publishes the session and
/// re-checks disposal under one lock, so exactly one side tears the session down.
///
public class DebugViewConnectDisposeRaceTests : BunitContext
{
///
/// A whose start can be held open on demand, so the
/// dispose-during-start window is deterministic rather than timing-dependent.
///
private sealed class GatedDebugStreamService : DebugStreamService
{
private readonly TaskCompletionSource _gate = new(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly TaskCompletionSource _entered = new(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly List _stopped = new();
public GatedDebugStreamService(
CommunicationService comms, IServiceProvider provider, SiteStreamGrpcClientFactory factory)
: base(comms, provider, factory, NullLogger.Instance)
{
}
public string SessionId => "session-under-test";
/// Completes once has been entered.
public Task Entered => _entered.Task;
/// Session ids passed to , in call order.
public IReadOnlyList Stopped
{
get { lock (_stopped) { return _stopped.ToList(); } }
}
public void ReleaseStart() => _gate.TrySetResult();
public override async Task StartStreamAsync(
int instanceId, Action