167 lines
6.6 KiB
C#
167 lines
6.6 KiB
C#
namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
|
|
|
|
public sealed class LegacyDatabaseHealthMonitorNativeContractTests
|
|
{
|
|
private static readonly string RepositoryRoot = FindRepositoryRoot();
|
|
private static readonly string AppRoot = Path.Combine(
|
|
RepositoryRoot,
|
|
"src",
|
|
"MBN_STOCK_WEBVIEW.LegacyParityApp");
|
|
private static readonly string Window = File.ReadAllText(
|
|
Path.Combine(AppRoot, "MainWindow.xaml.cs"));
|
|
private static readonly string Monitor = File.ReadAllText(
|
|
Path.Combine(AppRoot, "MainWindow.DatabaseHealth.cs"));
|
|
private static readonly string Playout = File.ReadAllText(
|
|
Path.Combine(AppRoot, "MainWindow.Playout.cs"));
|
|
|
|
[Fact]
|
|
public void LegacyTenSecondMonitorStartsAndStopsWithTheActiveWindow()
|
|
{
|
|
Assert.Contains(
|
|
"TimeSpan.FromSeconds(10)",
|
|
Monitor,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"await Task.Delay(DatabaseHealthPollingInterval, cancellationToken)",
|
|
Monitor,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"StartDatabaseHealthMonitor();",
|
|
Window,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"_lifetimeCancellation.Cancel();\n CancelActiveDatabaseHealthCheck();",
|
|
Normalize(Window),
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void MonitorUsesBothConfiguredProvidersAndLogsOnlyClosedTransitions()
|
|
{
|
|
Assert.Contains(
|
|
".HealthService\n .CheckAllAsync(healthCancellation.Token)",
|
|
Normalize(Monitor),
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"foreach (var status in statuses)",
|
|
Monitor,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"ObserveDatabaseHealthState(status.Source, status.State);",
|
|
Monitor,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"_databaseHealthTransitions.TryObserveDatabaseState(",
|
|
Monitor,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"_databaseHealthLogWriter.TryWrite(record)",
|
|
Monitor,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("status.UserMessage", Monitor, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("exception.Message", Monitor, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void OperatorAndRefreshWorkCancelAndSerializeAgainstHealthProbes()
|
|
{
|
|
var dispatch = Slice(
|
|
Window,
|
|
"private async Task ProcessIntentAsync(",
|
|
"private static bool IsNamedPlaylistModalIntent(");
|
|
var databaseWait = RequiredIndex(
|
|
dispatch,
|
|
"await AcquirePriorityDatabaseActivityAsync(_lifetimeCancellation.Token)");
|
|
var handle = RequiredIndex(dispatch, "await HandleIntentAsync(");
|
|
Assert.True(databaseWait < handle);
|
|
Assert.Contains("_databaseActivityGate.Release();", dispatch,
|
|
StringComparison.Ordinal);
|
|
|
|
var refresh = Slice(
|
|
Playout,
|
|
"private async Task RunRefreshLoopAsync(",
|
|
"private bool TrySetRefreshFault(");
|
|
databaseWait = RequiredIndex(
|
|
refresh,
|
|
"await AcquirePriorityDatabaseActivityAsync(token)");
|
|
var refreshCall = RequiredIndex(refresh, "workflow.RefreshOnAirAsync");
|
|
Assert.True(databaseWait < refreshCall);
|
|
Assert.Contains("_databaseActivityGate.Release();", refresh,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void OperatorPriorityClosesTheHealthProbeCancellationPublishRace()
|
|
{
|
|
var acquire = Slice(
|
|
Monitor,
|
|
"private async Task AcquirePriorityDatabaseActivityAsync(",
|
|
"private void CancelActiveDatabaseHealthCheck()");
|
|
var waiterIncrement = RequiredIndex(
|
|
acquire,
|
|
"Interlocked.Increment(ref _databaseActivityPriorityWaiterCount);");
|
|
var cancel = RequiredIndex(acquire, "CancelActiveDatabaseHealthCheck();");
|
|
var gateWait = RequiredIndex(
|
|
acquire,
|
|
"await _databaseActivityGate.WaitAsync(cancellationToken)");
|
|
var waiterDecrement = RequiredIndex(
|
|
acquire,
|
|
"Interlocked.Decrement(ref _databaseActivityPriorityWaiterCount);");
|
|
Assert.True(waiterIncrement < cancel);
|
|
Assert.True(cancel < gateWait);
|
|
Assert.True(gateWait < waiterDecrement);
|
|
|
|
var probe = Slice(
|
|
Monitor,
|
|
"private async Task CheckAndLogDatabaseHealthAsync(",
|
|
"private async Task AcquirePriorityDatabaseActivityAsync(");
|
|
var preAcquirePriorityCheck = RequiredIndex(
|
|
probe,
|
|
"Volatile.Read(ref _databaseActivityPriorityWaiterCount) != 0");
|
|
var healthCancellationPublish = RequiredIndex(
|
|
probe,
|
|
"Interlocked.Exchange(\n ref _databaseHealthCheckCancellation,\n healthCancellation);");
|
|
var postPublishPriorityCheck = RequiredIndex(
|
|
probe,
|
|
"Volatile.Read(ref _databaseActivityPriorityWaiterCount) != 0",
|
|
healthCancellationPublish);
|
|
var providerProbe = RequiredIndex(probe, ".CheckAllAsync(healthCancellation.Token)");
|
|
Assert.True(preAcquirePriorityCheck < healthCancellationPublish);
|
|
Assert.True(healthCancellationPublish < postPublishPriorityCheck);
|
|
Assert.True(postPublishPriorityCheck < providerProbe);
|
|
}
|
|
|
|
private static string Normalize(string value) =>
|
|
value.Replace("\r\n", "\n", StringComparison.Ordinal);
|
|
|
|
private static string Slice(string value, string start, string end)
|
|
{
|
|
var startIndex = RequiredIndex(value, start);
|
|
var endIndex = RequiredIndex(value, end, startIndex + start.Length);
|
|
return value[startIndex..endIndex];
|
|
}
|
|
|
|
private static int RequiredIndex(string value, string needle, int startIndex = 0)
|
|
{
|
|
var index = value.IndexOf(needle, startIndex, StringComparison.Ordinal);
|
|
Assert.True(index >= 0, $"Required marker was not found: {needle}");
|
|
return index;
|
|
}
|
|
|
|
private static string FindRepositoryRoot()
|
|
{
|
|
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
|
while (directory is not null)
|
|
{
|
|
if (File.Exists(Path.Combine(directory.FullName, "MBN_STOCK_WEBVIEW.sln")))
|
|
{
|
|
return directory.FullName;
|
|
}
|
|
|
|
directory = directory.Parent;
|
|
}
|
|
|
|
throw new DirectoryNotFoundException("Repository root was not found.");
|
|
}
|
|
}
|