Files
MBN_STOCK_WEBVIEW/tests/MBN_STOCK_WEBVIEW.LegacyWeb.Tests/LegacyPlayoutNativeContractTests.cs

385 lines
16 KiB
C#

namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
public sealed class LegacyPlayoutNativeContractTests
{
private static readonly string RepositoryRoot = FindRepositoryRoot();
private static readonly string AppRoot = Path.Combine(
RepositoryRoot,
"src",
"MBN_STOCK_WEBVIEW.LegacyParityApp");
private static readonly string Playout = File.ReadAllText(
Path.Combine(AppRoot, "MainWindow.Playout.cs"));
private static readonly string Window = File.ReadAllText(
Path.Combine(AppRoot, "MainWindow.xaml.cs"));
private static readonly string Project = File.ReadAllText(
Path.Combine(AppRoot, "MBN_STOCK_WEBVIEW.LegacyParityApp.csproj"));
[Fact]
public void ParityAppComposesTheVerifiedThirtyFiveSceneRuntime()
{
Assert.Contains("LegacySceneRuntimeFactory.cs", Project, StringComparison.Ordinal);
Assert.Contains("LegacySceneRuntimeFactory.Create(", Playout,
StringComparison.Ordinal);
Assert.Contains("trustedManualSource: _manualNetSellStore", Playout,
StringComparison.Ordinal);
Assert.Contains("trustedViSource: _manualViStore", Playout,
StringComparison.Ordinal);
}
[Fact]
public void StartupDefaultBackgroundDependsOnAbsentLocalConfigAndSurfacesWarning()
{
Assert.Contains("File.Exists(", Playout, StringComparison.Ordinal);
Assert.Contains("PlayoutOptionsLoader.DefaultPath", Playout,
StringComparison.Ordinal);
Assert.Contains("LegacyParityStartupCompositionResolver.Resolve(", Playout,
StringComparison.Ordinal);
Assert.Contains("_playoutInitializationWarning ?? status?.Message", Playout,
StringComparison.Ordinal);
}
[Fact]
public void OperatorCommandsUseTheNativeWorkflowAndCSharpPlaylist()
{
Assert.Contains("_controller.CreatePlayoutRequest(", Playout,
StringComparison.Ordinal);
Assert.Contains("_compositionOptions!.Current.FadeDuration", Playout,
StringComparison.Ordinal);
Assert.Contains("workflow.PrepareAsync(", Playout, StringComparison.Ordinal);
Assert.Contains("workflow.TakeSelectedAsync(", Playout,
StringComparison.Ordinal);
Assert.DoesNotContain("workflow.TakeInAsync(", Playout, StringComparison.Ordinal);
Assert.DoesNotContain(
"string.IsNullOrWhiteSpace(workflow.State.PreparedCutCode)",
Playout,
StringComparison.Ordinal);
Assert.Contains("workflow.NextAsync(", Playout, StringComparison.Ordinal);
Assert.Contains("workflow.TakeOutAsync(", Playout, StringComparison.Ordinal);
Assert.Contains("_controller.ReflectSuccessfulPlayout(", Playout,
StringComparison.Ordinal);
Assert.Contains("workflow.State.CurrentEntryId", Playout,
StringComparison.Ordinal);
Assert.Contains("LegacyOperatorPlayoutCommand.Prepare or", Playout,
StringComparison.Ordinal);
Assert.Contains(
"command != LegacyOperatorPlayoutCommand.TakeOut",
Playout,
StringComparison.Ordinal);
Assert.Contains("LegacyWorkflowNextKind.PlaylistNext", Playout,
StringComparison.Ordinal);
Assert.Contains("LegacySceneRefreshPolicy.TryGetInterval", Playout,
StringComparison.Ordinal);
Assert.Contains("WaitForPlayCompletionAsync", Playout, StringComparison.Ordinal);
}
[Fact]
public void AutomaticRefreshUsesTheCoreSchedulerAndGenerationOwnedState()
{
Assert.Contains("new LegacyRefreshScheduler(", Playout, StringComparison.Ordinal);
Assert.Contains(
"LegacyRefreshEpoch<LegacyRefreshRuntimeStatus>",
Playout,
StringComparison.Ordinal);
Assert.Contains("_refreshEpoch.ReadState()", Playout, StringComparison.Ordinal);
Assert.Contains("_refreshEpoch.Replace(", Playout, StringComparison.Ordinal);
Assert.Contains("_refreshEpoch.TryUpdate(", Playout, StringComparison.Ordinal);
Assert.Contains("_refreshEpoch.TryComplete(", Playout, StringComparison.Ordinal);
Assert.DoesNotContain("Task.Delay(", Playout, StringComparison.Ordinal);
foreach (var obsoleteRawField in new[]
{
"_refreshCancellation",
"_refreshCts",
"_refreshStateGate",
"_refreshActive",
"_refreshCompletedCount",
"_refreshMaximumCount",
"_refreshLimitReached",
"_refreshNextAtUtc",
"_refreshLastSuccessAtUtc",
"_refreshFaultCode",
"_refreshMessage"
})
{
Assert.DoesNotContain(obsoleteRawField, Playout, StringComparison.Ordinal);
}
}
[Fact]
public void ZeroRefreshLimitReturnsBeforeCreatingAnEpochOrLoop()
{
var start = Slice(
Playout,
"private void StartRefreshLoop(LegacyPlayoutWorkflow workflow)",
"private void StopRefreshLoop()");
var limitCheck = RequiredIndex(start, "if (scheduler.HasReachedMaximum)");
var linkedEpoch = RequiredIndex(
start,
"CancellationTokenSource.CreateLinkedTokenSource(");
var loopAssignment = RequiredIndex(
start,
"_refreshTask = RunRefreshLoopAsync(workflow, scheduler, cancellation);");
Assert.True(limitCheck < linkedEpoch);
Assert.True(linkedEpoch < loopAssignment);
var zeroLimitBranch = start[limitCheck..linkedEpoch];
Assert.Contains("_refreshTask = Task.CompletedTask;", zeroLimitBranch,
StringComparison.Ordinal);
Assert.Contains("_refreshEpoch.Stop(", zeroLimitBranch, StringComparison.Ordinal);
Assert.Contains("return;", zeroLimitBranch, StringComparison.Ordinal);
Assert.DoesNotContain("RunRefreshLoopAsync(", zeroLimitBranch,
StringComparison.Ordinal);
}
[Fact]
public void AutomaticRefreshSerializesDbThenPlayoutAndPublishesBothBusyStates()
{
var loop = Slice(
Playout,
"private async Task RunRefreshLoopAsync(",
"private bool TrySetRefreshFault(");
var intentWait = RequiredIndex(loop, "await _intentGate.WaitAsync(token)");
var intentBusy = RequiredIndex(loop, "_intentBusy = true;", intentWait);
var intentBusyState = RequiredIndex(loop, "QueueOperatorState();", intentBusy);
var playoutWait = RequiredIndex(
loop,
"await _playoutCommandGate.WaitAsync(token)",
intentBusyState);
var playoutBusy = RequiredIndex(
loop,
"Interlocked.Exchange(ref _playoutBusy, 1);",
playoutWait);
var playoutBusyState = RequiredIndex(loop, "QueueOperatorState();", playoutBusy);
var refreshDispatch = RequiredIndex(
loop,
"workflow.RefreshOnAirAsync",
playoutBusyState);
Assert.True(intentWait < intentBusy);
Assert.True(intentBusy < intentBusyState);
Assert.True(intentBusyState < playoutWait);
Assert.True(playoutWait < playoutBusy);
Assert.True(playoutBusy < playoutBusyState);
Assert.True(playoutBusyState < refreshDispatch);
}
[Fact]
public void KnownRefreshInputFailuresStopWithoutQuarantineButUnknownsDoQuarantine()
{
var loop = Normalize(Slice(
Playout,
"private async Task RunRefreshLoopAsync(",
"private bool TrySetRefreshFault("));
var databaseFailure = Slice(
loop,
"catch (DatabaseInfrastructureException)",
"catch (LegacySceneDataException)");
var sceneDataFailure = Slice(
loop,
"catch (LegacySceneDataException)",
"catch (ArgumentException)");
var argumentFailure = Slice(
loop,
"catch (ArgumentException)",
"catch\n {");
var unknownFailure = Slice(
loop,
"catch\n {",
"finally");
AssertKnownFailureDoesNotQuarantine(
databaseFailure,
"DATABASE_UNAVAILABLE");
AssertKnownFailureDoesNotQuarantine(
sceneDataFailure,
"SCENE_DATA_INVALID");
AssertKnownFailureDoesNotQuarantine(
argumentFailure,
"SCENE_ARGUMENT_INVALID");
Assert.Contains("REFRESH_OUTCOME_UNKNOWN", unknownFailure,
StringComparison.Ordinal);
Assert.Contains("QuarantinePlayoutAsync", unknownFailure,
StringComparison.Ordinal);
Assert.Contains(
"result.Code is PlayoutResultCode.OutcomeUnknown or\n" +
" PlayoutResultCode.TimedOut",
loop,
StringComparison.Ordinal);
AssertEveryFaultBranchQuarantines(loop, "PLAY_CALLBACK_TIMEOUT");
}
[Fact]
public void ExternalIdleStatusResetsNormalRefreshState()
{
var handler = Slice(
Playout,
"private void OnPlayoutStatusChanged(",
"private async Task<LegacyOperatorSnapshot> ExecuteOperatorPlayoutAsync(");
Assert.Contains(
"string.IsNullOrWhiteSpace(args.Current.PreparedSceneName)",
handler,
StringComparison.Ordinal);
Assert.Contains(
"string.IsNullOrWhiteSpace(args.Current.OnAirSceneName)",
handler,
StringComparison.Ordinal);
Assert.Contains(
"args.Current.State == PlayoutConnectionState.OutcomeUnknown",
handler,
StringComparison.Ordinal);
var stop = RequiredIndex(handler, "StopRefreshLoop();");
var normalElse = RequiredIndex(handler, "else", stop);
var reset = RequiredIndex(handler, "ResetRefreshState();", normalElse);
Assert.True(stop < normalElse && normalElse < reset);
var resetHelper = Slice(
Playout,
"private void ResetRefreshState()",
"private async Task RunRefreshLoopAsync(");
Assert.Contains(
"_refreshEpoch.Stop(_ => LegacyRefreshRuntimeStatus.Empty",
resetHelper,
StringComparison.Ordinal);
}
[Fact]
public void UnknownOutcomeQuarantinesWithoutAutomaticCommandRetry()
{
Assert.Contains("PlayoutResultCode.OutcomeUnknown", Playout,
StringComparison.Ordinal);
Assert.Contains("PlayoutResultCode.TimedOut", Playout, StringComparison.Ordinal);
Assert.Contains("QuarantinePlayoutAsync", Playout, StringComparison.Ordinal);
Assert.DoesNotContain("MaximumReconnectAttempts", Playout,
StringComparison.Ordinal);
Assert.DoesNotContain("ReconnectAsync", Playout, StringComparison.Ordinal);
}
[Fact]
public void OriginalDissolveComboAcceptsOnlyItsZeroBasedTwentyItemIndex()
{
Assert.Contains("fadeDuration is < 0 or > 19", Playout,
StringComparison.Ordinal);
Assert.DoesNotContain("fadeDuration is < 0 or > 60", Playout,
StringComparison.Ordinal);
}
[Fact]
public void HomeAndEndStopAutomaticRefreshBeforeChangingTheCandidateRow()
{
Assert.Contains(
"LegacySelectPlaylistBoundaryIntent boundary =>\n" +
" SelectPlaylistBoundaryAndStopRefresh(boundary.Last)",
Window.Replace("\r\n", "\n", StringComparison.Ordinal),
StringComparison.Ordinal);
Assert.Contains(
"private LegacyOperatorSnapshot SelectPlaylistBoundaryAndStopRefresh(bool last)",
Playout,
StringComparison.Ordinal);
var helperStart = Playout.IndexOf(
"private LegacyOperatorSnapshot SelectPlaylistBoundaryAndStopRefresh(bool last)",
StringComparison.Ordinal);
var helperEnd = Playout.IndexOf(
"private async Task<LegacyOperatorSnapshot> ToggleBackgroundAsync",
helperStart,
StringComparison.Ordinal);
Assert.True(helperStart >= 0 && helperEnd > helperStart);
var helper = Playout[helperStart..helperEnd];
Assert.True(
helper.IndexOf("StopRefreshLoop();", StringComparison.Ordinal) <
helper.IndexOf("_controller.SelectPlaylistBoundary(last)", StringComparison.Ordinal));
}
[Fact]
public void PreparedProgramAndUnknownWriteOutcomesBlockOperatorMutations()
{
Assert.Contains("CanAcceptOperatorMutation()", Window, StringComparison.Ordinal);
Assert.Contains("IsOperatorMutationQuarantined()", Window,
StringComparison.Ordinal);
Assert.Contains("ObserveOperatorMutationQuarantine(state)", Window,
StringComparison.Ordinal);
Assert.Contains("ManualFinancial?.WritesQuarantined", Playout,
StringComparison.Ordinal);
Assert.Contains("NamedPlaylist?.IsWriteQuarantined", Playout,
StringComparison.Ordinal);
Assert.Contains("ManualLists?.WritesQuarantined", Playout,
StringComparison.Ordinal);
Assert.Contains("OperatorCatalog?.WritesQuarantined", Playout,
StringComparison.Ordinal);
Assert.Contains("LegacyConfirmManualListIntent", Playout,
StringComparison.Ordinal);
Assert.Contains("LegacyCutPointerDownIntent", Playout,
StringComparison.Ordinal);
Assert.Contains("LegacyClearCutSelectionIntent", Playout,
StringComparison.Ordinal);
}
private static void AssertKnownFailureDoesNotQuarantine(
string failureBlock,
string expectedFaultCode)
{
Assert.Contains(expectedFaultCode, failureBlock, StringComparison.Ordinal);
Assert.DoesNotContain(
"QuarantinePlayoutAsync",
failureBlock,
StringComparison.Ordinal);
}
private static void AssertEveryFaultBranchQuarantines(
string source,
string faultCode)
{
var occurrence = 0;
var count = 0;
while ((occurrence = source.IndexOf(
faultCode,
occurrence,
StringComparison.Ordinal)) >= 0)
{
var branchEnd = source.IndexOf("return;", occurrence, StringComparison.Ordinal);
Assert.True(branchEnd > occurrence, $"{faultCode} branch has no return.");
Assert.Contains(
"QuarantinePlayoutAsync",
source[occurrence..branchEnd],
StringComparison.Ordinal);
count++;
occurrence = branchEnd + "return;".Length;
}
Assert.True(count > 0, $"No {faultCode} branch was found.");
}
private static string Slice(string source, string startMarker, string endMarker)
{
var start = RequiredIndex(source, startMarker);
var end = RequiredIndex(source, endMarker, start + startMarker.Length);
return source[start..end];
}
private static int RequiredIndex(string source, string marker, int startIndex = 0)
{
var index = source.IndexOf(marker, startIndex, StringComparison.Ordinal);
Assert.True(index >= 0, $"Required source marker was not found: {marker}");
return index;
}
private static string Normalize(string source) =>
source.Replace("\r\n", "\n", StringComparison.Ordinal);
private static string FindRepositoryRoot()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
{
if (File.Exists(Path.Combine(current.FullName, "MBN_STOCK_WEBVIEW.sln")))
{
return current.FullName;
}
current = current.Parent;
}
throw new DirectoryNotFoundException("Repository root was not found.");
}
}