using System.Collections.Concurrent; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using ZB.MOM.WW.ScadaBridge.SiteEventLogging; using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts; namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.TestSupport; /// /// A capturing fake /// used by the actor tests to assert that the right operational events are emitted. /// Thread-safe — the actors fire-and-forget LogEventAsync from background /// tasks, so multiple captures can land concurrently. /// public sealed class FakeSiteEventLogger : ISiteEventLogger { /// One captured invocation. public sealed record Entry( string EventType, string Severity, string? InstanceId, string Source, string Message, string? Details); private readonly ConcurrentQueue _entries = new(); /// All captured events, in arrival order. public IReadOnlyList Entries => _entries.ToArray(); /// Captured events filtered to a single category. /// The event category to filter on. /// The captured events with the given category. public IReadOnlyList OfType(string eventType) => _entries.Where(e => e.EventType == eventType).ToArray(); // Captures the event so tests can assert on it. /// public Task LogEventAsync( string eventType, string severity, string? instanceId, string source, string message, string? details = null) { _entries.Enqueue(new Entry(eventType, severity, instanceId, source, message, details)); return Task.CompletedTask; } // Always zero for this fake. /// public long FailedWriteCount => 0; } /// /// Minimal that resolves a single /// — enough for the actors' optional /// _serviceProvider?.GetService<ISiteEventLogger>() resolution /// without pulling a full DI container into the actor tests. /// /// Also serves (returning a scope that just /// re-exposes this provider) so callers that do /// serviceProvider.CreateScope() — e.g. ScriptExecutionActor — /// don't throw before they reach the logging hot path. /// /// /// The event logger to resolve for . /// /// Optional for /// — resolves for the hot-toggle tests; null (the default) exercises the launcher's /// constructor-passed-options fallback, matching every earlier caller of this class. /// /// /// Optional — resolves so a test can assert on /// the aggregate counters a run produced; null (the default) exercises the launcher's /// null-safe skip path. /// public sealed class SingleServiceProvider( ISiteEventLogger logger, IOptionsMonitor? optionsMonitor = null, ScriptRunSummaryRecorder? summaryRecorder = null) : IServiceProvider, IServiceScopeFactory, IServiceScope { private readonly ISiteEventLogger _logger = logger; private readonly IOptionsMonitor? _optionsMonitor = optionsMonitor; private readonly ScriptRunSummaryRecorder? _summaryRecorder = summaryRecorder; /// Resolves the logger, the scope factory and the optional options monitor and summary recorder. /// The service type to resolve. /// The matching service, or null when the type is not served. public object? GetService(Type serviceType) { if (serviceType == typeof(ISiteEventLogger)) return _logger; if (serviceType == typeof(IServiceScopeFactory)) return this; if (serviceType == typeof(IOptionsMonitor)) return _optionsMonitor; if (serviceType == typeof(ScriptRunSummaryRecorder)) return _summaryRecorder; return null; } /// Creates a scope that re-exposes this provider. /// This instance as the scope. public IServiceScope CreateScope() => this; /// Gets the provider for this scope, which is this instance. public IServiceProvider ServiceProvider => this; /// Does nothing; the scope owns no resources. public void Dispose() { } }