682 lines
31 KiB
C#
682 lines
31 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 BackgroundPicker = File.ReadAllText(
|
|
Path.Combine(AppRoot, "MainWindow.BackgroundPicker.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 PlayoutIntentsWaitBehindEarlierUiWorkWithoutQueuingDuplicates()
|
|
{
|
|
var process = Normalize(Slice(
|
|
Window,
|
|
"private async Task ProcessIntentAsync(LegacyUiIntent intent)",
|
|
"private static bool IsOrderedPlayoutIntent"));
|
|
var classifier = Slice(
|
|
Window,
|
|
"private static bool IsOrderedPlayoutIntent",
|
|
"private static bool IsNamedPlaylistModalIntent");
|
|
|
|
var admission = RequiredIndex(
|
|
process,
|
|
"_orderedPlayoutIntentGate.WaitAsync(");
|
|
var orderedWait = RequiredIndex(
|
|
process,
|
|
"await _intentGate.WaitAsync(_lifetimeCancellation.Token)",
|
|
admission);
|
|
var zeroTimeoutWait = RequiredIndex(
|
|
process,
|
|
"gateEntered = await _intentGate.WaitAsync(\n 0,",
|
|
orderedWait);
|
|
var intentRelease = RequiredIndex(process, "_intentGate.Release();", zeroTimeoutWait);
|
|
var admissionRelease = RequiredIndex(
|
|
process,
|
|
"_orderedPlayoutIntentGate.Release();",
|
|
intentRelease);
|
|
|
|
Assert.True(admission < orderedWait);
|
|
Assert.True(orderedWait < zeroTimeoutWait);
|
|
Assert.True(zeroTimeoutWait < intentRelease);
|
|
Assert.True(intentRelease < admissionRelease);
|
|
Assert.Contains("if (orderedPlayoutIntentEntered)", process,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("LegacyExecutePlayoutIntent", classifier,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("LegacyGateAPrepareIntent", classifier,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("_lifetimeCancellation.Token", process,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void LocalSelectionDoubleClickAndCutDropQueueBehindRefreshInArrivalOrder()
|
|
{
|
|
var process = Normalize(Slice(
|
|
Window,
|
|
"private async Task ProcessIntentAsync(LegacyUiIntent intent)",
|
|
"private LegacyOperatorSnapshot SelectCutWithoutAppend("));
|
|
var classifier = Slice(
|
|
Window,
|
|
"private static bool IsQueuedLocalSelectionIntent",
|
|
"private static bool IsOperatorSettingsIntent");
|
|
|
|
Assert.Contains("_orderedLocalSelectionIntentGate.WaitAsync(", process,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("else if (IsQueuedLocalSelectionIntent(intent))", process,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("await _intentGate.WaitAsync(_lifetimeCancellation.Token)", process,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("_orderedLocalSelectionIntentGate.Release();", process,
|
|
StringComparison.Ordinal);
|
|
|
|
Assert.Contains("LegacySelectStockIntent", classifier, StringComparison.Ordinal);
|
|
Assert.Contains("LegacySelectOperatorCatalogStockIntent", classifier,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("LegacyActivateOperatorCatalogStockIntent", classifier,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("LegacyCutPointerDownIntent", classifier, StringComparison.Ordinal);
|
|
Assert.Contains("LegacyDropSelectedCutsIntent", classifier, StringComparison.Ordinal);
|
|
Assert.Contains("LegacyCutKeyDownIntent", classifier, StringComparison.Ordinal);
|
|
Assert.Contains("LegacyClearCutSelectionIntent", classifier, StringComparison.Ordinal);
|
|
Assert.Contains("LegacySelectPlaylistRowIntent", classifier, StringComparison.Ordinal);
|
|
Assert.Contains("LegacyActivatePlaylistRowIntent", classifier, StringComparison.Ordinal);
|
|
Assert.Contains("LegacySelectPlaylistBoundaryIntent", classifier, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("LegacySearchStocksIntent", classifier, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("LegacyExecutePlayoutIntent", classifier, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("LegacySave", classifier, 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 BackgroundControlsStageTheNextCueWithoutChangingTheOnAirRefreshSource()
|
|
{
|
|
Assert.Contains("_stagedCompositionOptions", Playout, StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"_stagedCompositionOptions = new MutableLegacySceneCueCompositionOptionsSource(\n" +
|
|
" initialComposition);",
|
|
Normalize(Playout),
|
|
StringComparison.Ordinal);
|
|
|
|
var toggle = Slice(
|
|
Playout,
|
|
"private async Task<LegacyOperatorSnapshot> ToggleBackgroundAsync(",
|
|
"private async Task<LegacyOperatorSnapshot> ChooseBackgroundAsync(");
|
|
Assert.Contains("if (!CanStageBackground())", toggle, StringComparison.Ordinal);
|
|
Assert.Contains("var source = _stagedCompositionOptions!;", toggle,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("var source = _compositionOptions!;", toggle,
|
|
StringComparison.Ordinal);
|
|
|
|
var stageGuard = Slice(
|
|
Playout,
|
|
"private bool CanStageBackground()",
|
|
"private void ApplyStagedCompositionForNextCue()");
|
|
Assert.Contains("status.State != PlayoutConnectionState.OutcomeUnknown", stageGuard,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("!status.IsPlayCompletionPending", stageGuard,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("!status.IsTakeOutCompletionPending", stageGuard,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("PreparedSceneName", stageGuard, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("OnAirSceneName", stageGuard, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("refresh.IsActive", stageGuard, StringComparison.Ordinal);
|
|
|
|
var execute = Slice(
|
|
Playout,
|
|
"private async Task<LegacyOperatorSnapshot> ExecuteOperatorPlayoutAsync(",
|
|
"private async Task<LegacyOperatorPlayoutRequest> CreateFreshPagedPlayoutRequestAsync(");
|
|
var stopRefresh = RequiredIndex(execute, "StopRefreshLoop();");
|
|
var promote = RequiredIndex(execute, "ApplyStagedCompositionForNextCue();");
|
|
var dispatch = RequiredIndex(execute, "switch (command)");
|
|
Assert.True(stopRefresh < promote && promote < dispatch);
|
|
Assert.Contains("LegacyOperatorPlayoutCommand.Prepare or", execute,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("LegacyOperatorPlayoutCommand.TakeIn or", execute,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("LegacyOperatorPlayoutCommand.Next", execute,
|
|
StringComparison.Ordinal);
|
|
|
|
var promotion = Slice(
|
|
Playout,
|
|
"private void ApplyStagedCompositionForNextCue()",
|
|
"private bool CanAcceptOperatorMutation(");
|
|
Assert.Contains("active.Update(staged.Current);", promotion,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("var composition = _stagedCompositionOptions?.Current", Playout,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("CanStageBackground(),", Playout, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void F2PickerStartsInsideTheValidatedBackgroundRootAndKeepsClosedExtensions()
|
|
{
|
|
var choose = Slice(
|
|
Playout,
|
|
"private async Task<LegacyOperatorSnapshot> ChooseBackgroundAsync(",
|
|
"private bool CanChangeComposition()");
|
|
Assert.Contains("TryGetTrustedBackgroundDirectory(", choose,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("PickBackgroundFileAsync(\n trustedBackgroundDirectory)",
|
|
Normalize(choose), StringComparison.Ordinal);
|
|
Assert.DoesNotContain("PickerLocationId.PicturesLibrary", Playout,
|
|
StringComparison.Ordinal);
|
|
|
|
Assert.Contains("dialog.SetFolder(initialFolder);", BackgroundPicker,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("dialog.SetDefaultFolder(initialFolder);", BackgroundPicker,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("*.vrv;*.jpg;*.jpeg;*.png", BackgroundPicker,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("FileOpenOptions.ForceFileSystem", BackgroundPicker,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("FileOpenOptions.NoDereferenceLinks", BackgroundPicker,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("WindowNative.GetWindowHandle(this)", BackgroundPicker,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("DispatcherQueue.HasThreadAccess", BackgroundPicker,
|
|
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 ProgramAllowsOriginalEnabledGesturesAndSafeTailAppendButKeepsStructuralEditsLocked()
|
|
{
|
|
Assert.Contains("CanAcceptOperatorMutation(intent, out var mutationRejectionMessage)",
|
|
Window, StringComparison.Ordinal);
|
|
Assert.Contains("CanAcceptOperatorMutation(", Playout, StringComparison.Ordinal);
|
|
Assert.Contains("RequiresIdlePlaylistMutation(intent)", Playout,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("CanDeleteSelectedPlaylistTail(status, workflow)", Playout,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("isSafeCutTailAppend", Playout, StringComparison.Ordinal);
|
|
Assert.Contains("CanAppendCutToPlaylistTail(status, workflow)", Playout,
|
|
StringComparison.Ordinal);
|
|
var safeCutTailClassifier = Slice(
|
|
Playout,
|
|
"var isSafeCutTailAppend",
|
|
"// MainForm kept its Space");
|
|
Assert.Contains("LegacyCutPointerDownIntent { AllowAppend: true }",
|
|
safeCutTailClassifier, StringComparison.Ordinal);
|
|
Assert.Contains("LegacyDropSelectedCutsIntent", safeCutTailClassifier,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("isSafeProgramEnabledStateMutation", Playout,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("isSafeProgramTailDelete", Playout,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("refreshAllowsMutation", Playout, StringComparison.Ordinal);
|
|
Assert.Contains("!status.IsPlayCompletionPending", Playout, StringComparison.Ordinal);
|
|
Assert.Contains("!status.IsTakeOutCompletionPending", Playout, StringComparison.Ordinal);
|
|
Assert.Contains("status.State != PlayoutConnectionState.OutcomeUnknown", Playout,
|
|
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("CanMutatePlaylistEnabledState(status, workflow)", Playout,
|
|
StringComparison.Ordinal);
|
|
var idleOnlyMutations = Slice(
|
|
Playout,
|
|
"private static bool RequiresIdlePlaylistMutation",
|
|
"private static bool IsPlaylistMutationIdle");
|
|
Assert.Contains("LegacySetPlaylistEnabledIntent", idleOnlyMutations,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("LegacySetAllPlaylistEnabledIntent", idleOnlyMutations,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("LegacyToggleActivePlaylistEnabledIntent", idleOnlyMutations,
|
|
StringComparison.Ordinal);
|
|
var programEnabledAdmission = Slice(
|
|
Playout,
|
|
"private bool CanMutatePlaylistEnabledState(",
|
|
"private static bool IsOperatorMutationIntent");
|
|
Assert.Contains("status.OnAirSceneName", programEnabledAdmission,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("workflow.OnAirCutCode", programEnabledAdmission,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("workflow.CurrentEntryId", programEnabledAdmission,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("workflow.CurrentCueIndexZeroBased", programEnabledAdmission,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("IsPlaylistMutationIdle(status, workflow)", programEnabledAdmission,
|
|
StringComparison.Ordinal);
|
|
var nextKindProjection = Slice(
|
|
Playout,
|
|
"private LegacyWorkflowNextKind ResolveOperatorNextKind(",
|
|
"private LegacyOperatorPlayoutSnapshot CreatePlayoutSnapshot()");
|
|
Assert.Contains("workflow.IsLastPage", nextKindProjection,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("LegacyWorkflowNextKind.PageNext", nextKindProjection,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("Skip(currentIndex + 1)", nextKindProjection,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("Any(static row => row.IsEnabled)", nextKindProjection,
|
|
StringComparison.Ordinal);
|
|
var mutationClassifier = Slice(
|
|
Playout,
|
|
"private static bool IsOperatorMutationIntent",
|
|
"private static bool IsFixedSectionBatchMutationIntent");
|
|
Assert.Contains("LegacyCutPointerDownIntent { AllowAppend: true }", mutationClassifier,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("LegacyDropSelectedCutsIntent", mutationClassifier,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("LegacyCutKeyDownIntent", mutationClassifier,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("LegacyClearCutSelectionIntent", mutationClassifier,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("pointer.AllowAppend", Window,
|
|
StringComparison.Ordinal);
|
|
Assert.Equal(2, Window.Split(
|
|
"PostState(SelectCutWithoutAppend(cutPointer));",
|
|
StringSplitOptions.None).Length - 1);
|
|
var selectionOnly = Slice(
|
|
Window,
|
|
"private LegacyOperatorSnapshot SelectCutWithoutAppend(",
|
|
"private static bool IsOrderedPlayoutIntent");
|
|
Assert.Contains("allowAppend: false", selectionOnly,
|
|
StringComparison.Ordinal);
|
|
var queuedLocalIntents = Slice(
|
|
Window,
|
|
"private static bool IsQueuedLocalSelectionIntent",
|
|
"private static bool IsOperatorSettingsIntent");
|
|
Assert.Contains("LegacyDropSelectedCutsIntent", queuedLocalIntents,
|
|
StringComparison.Ordinal);
|
|
var safeTailAdmission = Slice(
|
|
Playout,
|
|
"private bool CanAppendCutToPlaylistTail(",
|
|
"private static bool RequiresIdlePlaylistMutation");
|
|
Assert.Contains("status.OnAirSceneName", safeTailAdmission,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("workflow.OnAirCutCode", safeTailAdmission,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("workflow.CurrentEntryId", safeTailAdmission,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("workflow.CurrentCueIndexZeroBased", safeTailAdmission,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("currentIndex < playlist.Count", safeTailAdmission,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("playlist.Count(row => string.Equals(", safeTailAdmission,
|
|
StringComparison.Ordinal);
|
|
|
|
var orderedTailDelete = Slice(
|
|
Window,
|
|
"else if (IsOrderedPlaylistTailMutationIntent(intent))",
|
|
"else if (IsQueuedLocalSelectionIntent(intent))");
|
|
Assert.Contains("_orderedPlaylistTailMutationIntentGate.WaitAsync(",
|
|
orderedTailDelete, StringComparison.Ordinal);
|
|
Assert.Contains("0,", orderedTailDelete, StringComparison.Ordinal);
|
|
Assert.Contains("_orderedLocalSelectionIntentGate.WaitAsync(",
|
|
orderedTailDelete, StringComparison.Ordinal);
|
|
Assert.Contains("await _intentGate.WaitAsync", orderedTailDelete,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"intent is LegacyDeleteSelectedPlaylistRowsIntent",
|
|
Window,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("_orderedPlaylistTailMutationIntentGate.Release();",
|
|
Window,
|
|
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.");
|
|
}
|
|
}
|