feat: add audited one-shot prepare gate
This commit is contained in:
@@ -4,6 +4,7 @@ using MBN_STOCK_WEBVIEW.Playout.Configuration;
|
||||
using MBN_STOCK_WEBVIEW.Playout.Diagnostics;
|
||||
using MBN_STOCK_WEBVIEW.Playout.Interop;
|
||||
using MBN_STOCK_WEBVIEW.Playout.Runtime;
|
||||
using MBN_STOCK_WEBVIEW.Playout.Safety;
|
||||
|
||||
namespace MBN_STOCK_WEBVIEW.Playout.Tests;
|
||||
|
||||
@@ -1678,6 +1679,128 @@ public sealed class DynamicK3dSessionTests
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:EventHandler"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("mutation")]
|
||||
[InlineData("query")]
|
||||
[InlineData("end")]
|
||||
public async Task GateA_PrepareStageFailureNeverRollsBackDisconnectsOrRetries(
|
||||
string failureStage)
|
||||
{
|
||||
const string capability =
|
||||
"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
|
||||
using var scenes = TemporarySceneDirectory.Create("5001.t2s");
|
||||
var rawOptions = new PlayoutOptions
|
||||
{
|
||||
Mode = PlayoutMode.Live,
|
||||
Host = "127.0.0.1",
|
||||
Port = 30001,
|
||||
TcpMode = 1,
|
||||
LayoutIndex = 10,
|
||||
SceneDirectory = scenes.Path,
|
||||
TestSceneAllowlist = ["5001"],
|
||||
TrustedLiveOutputEnabled = true,
|
||||
ReconnectEnabled = false,
|
||||
MaximumReconnectAttempts = 0,
|
||||
MaximumAutomaticRefreshesPerTakeIn = 0,
|
||||
LegacySceneFadeDuration = 6,
|
||||
ConnectTimeoutMilliseconds = 500,
|
||||
OperationTimeoutMilliseconds = 500,
|
||||
DisconnectTimeoutMilliseconds = 500,
|
||||
ProcessPollIntervalMilliseconds = 100
|
||||
};
|
||||
var options = ValidatedPlayoutOptions.Create(rawOptions);
|
||||
var failure = new COMException($"fake Gate A {failureStage} failure");
|
||||
var log = new FakeComLog();
|
||||
var scene = new FakeScene(log, "5001");
|
||||
var player = new FakePlayer(log);
|
||||
var vendorEngine = new FakeEngine(log, player, scene);
|
||||
switch (failureStage)
|
||||
{
|
||||
case "mutation":
|
||||
scene.GetObjectFailure = failure;
|
||||
break;
|
||||
case "query":
|
||||
scene.QueryVariablesFailure = failure;
|
||||
break;
|
||||
case "end":
|
||||
vendorEngine.EndTransactionFailure = failure;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Unknown test failure stage.");
|
||||
}
|
||||
|
||||
var activator = new FakeActivator(log, vendorEngine);
|
||||
var releaser = new FakeReleaser(
|
||||
log,
|
||||
(scene, "5001"),
|
||||
(player, "Player"),
|
||||
(vendorEngine, "Engine"),
|
||||
(activator.EventHandler, "EventHandler"));
|
||||
var sessionFactory = new DelegateK3dSessionFactory(() =>
|
||||
new DynamicK3dSession(
|
||||
activator,
|
||||
releaser,
|
||||
new InstalledK3dInteropMethodInvoker(),
|
||||
new ActivatorK3dEventHandlerFactory(activator),
|
||||
rollbackOnPrepareFailure: false));
|
||||
using var authorization = PlayoutLaunchAuthorization.CreateGateAForTests(
|
||||
capability);
|
||||
var dispatcher = new StaDispatcher(options.QueueCapacity);
|
||||
var engine = new TornadoPlayoutEngine(
|
||||
options,
|
||||
sessionFactory,
|
||||
new FakeRegistrationProbe(),
|
||||
new FakeTornadoProcessProbe(
|
||||
ProcessSnapshot("approved-pgm"),
|
||||
ProcessSnapshot("approved-pgm")),
|
||||
dispatcher,
|
||||
authorization,
|
||||
TimeProvider.System,
|
||||
startMonitor: false,
|
||||
finalConnectSafetyCheck: () => true,
|
||||
abandonOnTargetMismatch: true,
|
||||
beforeSdkDispatchClaim: authorization.EnsureGateASdkDispatchAuthorized,
|
||||
launchAuthorization: authorization);
|
||||
|
||||
try
|
||||
{
|
||||
Assert.True((await engine.ConnectAsync(CancellationToken.None)).IsSuccess);
|
||||
Assert.True(authorization.TryClaimGateAPrepare(capability));
|
||||
var cue = new PlayoutCue(
|
||||
"5001.t2s",
|
||||
"5001",
|
||||
[new PlayoutField("headline", "test", true)],
|
||||
FadeDuration: 6,
|
||||
Mutations: [new PlayoutUseBackground(false)]);
|
||||
|
||||
var result = await engine.PrepareAsync(cue, CancellationToken.None);
|
||||
var retry = await engine.PrepareAsync(cue, CancellationToken.None);
|
||||
|
||||
Assert.Equal(PlayoutResultCode.OutcomeUnknown, result.Code);
|
||||
Assert.Equal(PlayoutResultCode.OutcomeUnknown, retry.Code);
|
||||
}
|
||||
finally
|
||||
{
|
||||
authorization.CompleteGateAPrepare();
|
||||
await engine.DisposeAsync();
|
||||
}
|
||||
|
||||
Assert.Equal(1, log.Names.Count(name => name == "BeginTransaction"));
|
||||
Assert.DoesNotContain("RollbackTransaction", log.Names);
|
||||
Assert.DoesNotContain("Disconnect", log.Names);
|
||||
Assert.DoesNotContain("Prepare:10", log.Names);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:5001"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:Player"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:Engine"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:EventHandler"));
|
||||
}
|
||||
|
||||
private static TornadoProcessSnapshot ProcessSnapshot(string generation) =>
|
||||
new(1, 1, 0)
|
||||
{
|
||||
EligibleProcessGeneration = new TornadoProcessGeneration(generation)
|
||||
};
|
||||
|
||||
private static PlayoutOptions TestOptions(string sceneDirectory) => new()
|
||||
{
|
||||
Mode = PlayoutMode.Test,
|
||||
@@ -1715,6 +1838,12 @@ public sealed class DynamicK3dSessionTests
|
||||
name.StartsWith("Unload:", StringComparison.Ordinal) ||
|
||||
name.StartsWith("Release:500", StringComparison.Ordinal)).ToArray();
|
||||
|
||||
private sealed class DelegateK3dSessionFactory(Func<IK3dSession> create)
|
||||
: IK3dSessionFactory
|
||||
{
|
||||
public IK3dSession Create() => create();
|
||||
}
|
||||
|
||||
public sealed class FakeComLog
|
||||
{
|
||||
private readonly ConcurrentQueue<string> _names = new();
|
||||
@@ -1865,6 +1994,8 @@ public sealed class DynamicK3dSessionTests
|
||||
|
||||
public Func<string, FakeScene>? SceneResolver { get; init; }
|
||||
|
||||
public Exception? EndTransactionFailure { get; set; }
|
||||
|
||||
public int KTAPConnect(
|
||||
int tcpMode,
|
||||
string host,
|
||||
@@ -1913,10 +2044,23 @@ public sealed class DynamicK3dSessionTests
|
||||
|
||||
public void BeginTransaction() => _log.Add("BeginTransaction");
|
||||
|
||||
public void EndTransaction() => _log.Add("EndTransaction");
|
||||
public void EndTransaction()
|
||||
{
|
||||
_log.Add("EndTransaction");
|
||||
if (EndTransactionFailure is not null)
|
||||
{
|
||||
throw EndTransactionFailure;
|
||||
}
|
||||
}
|
||||
|
||||
public void EndTransactionOnChannel(int channel) =>
|
||||
public void EndTransactionOnChannel(int channel)
|
||||
{
|
||||
_log.Add($"EndTransactionOnChannel:{channel}");
|
||||
if (EndTransactionFailure is not null)
|
||||
{
|
||||
throw EndTransactionFailure;
|
||||
}
|
||||
}
|
||||
|
||||
public void RollbackTransaction() => _log.Add("RollbackTransaction");
|
||||
|
||||
@@ -2026,6 +2170,11 @@ public sealed class DynamicK3dSessionTests
|
||||
public object GetObject(string objectName)
|
||||
{
|
||||
_log.Add($"GetObject:{objectName}");
|
||||
if (GetObjectFailure is not null)
|
||||
{
|
||||
throw GetObjectFailure;
|
||||
}
|
||||
|
||||
return new FakeSceneObject(_log, objectName);
|
||||
}
|
||||
|
||||
@@ -2044,6 +2193,8 @@ public sealed class DynamicK3dSessionTests
|
||||
set => _queryVariablesFailure = value;
|
||||
}
|
||||
|
||||
public Exception? GetObjectFailure { get; set; }
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
_log.Add(_name is null ? "Unload" : $"Unload:{_name}");
|
||||
|
||||
Reference in New Issue
Block a user